This is part six of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.

See part 1 if you want to catch the series from the start.

Last time, we made our first test succeed. This time we’ll put some finishing touches our source file to better work with shpec. This will form the outline of a testable script.

Main Street

While our hello-world script has a function, it’s not a runnable script. If you tried to run it, it would do nothing.

Following the tradition of creating a main as the entry point to the logic:

#!/usr/bin/env bash

main () {
  hello_world
}

hello_world () {
  echo "hello, world!"
}

main "$@"

The functions are defined at the top, and then main is finally put into action where it is called at the bottom. Any script arguments are handed to main via "$@".

With more sophisticated scripts which take switches and named arguments, it’s acceptable to parse those outside of main and pass the processed values to main.

To Run or Not to Run

Now that we have a runnable script, our test won’t work properly since it relies on the source file to not actually do anything when we source it. Now the script will run main when we source it in test, which is certainly undesirable.

We can make it do the appropriate thing both in test when sourced, as well as in practice when run, by detecting whether the script has been sourced:

#!/usr/bin/env bash

main () {
  hello_world
}

hello_world () {
  echo "hello, world!"
}

sourced () {
  [[ ${FUNCNAME[1]} == source ]]
}

sourced && return

main "$@"

Although it looks a bit strange to create a sourced function for a single use right afterward, as you can probably guess, this function is normally in a utility library used by almost every script I write.

The sourced function simply looks at the name of the function two levels up in the call stack, which happens to be the function which invoked the script. When bash sources a file, it sets that name to “source”. Otherwise the program is being run as a script.

So if sourced is true, then the sourced && return statement stops the sourcing and returns to the caller, in this case our shpec test, so that main "$@" never gets run.

Continue with part 7 - sourcing