Faking try/catch/finally in bourne shell (and jenkins)

When Bourne shell was first release in 1977 it turned out that, for several very good reasons, Steven Bourne had designed a nice simple language with no need for exception handling. That is, it did not need exception handling until Jenkins, also for very good reasons started using it with the -ex that causes the shell to bail out on the first error in encounters.

Normally Jenkins’ behaviour is is exactly what you need. Scripts stop as soon as something goes wrong. However a typical glue script to run a test suite overnight on a shared development board might look like the following pseudo-code:

lock $MY_DEVELOPMENT_BOARD
make test TARGET=$MY_DEVELOPMENT_BOARD
unlock $MY_DEVELOPMENT_BOARD

This problem with the above code is run using jenkins, or any other tool that runs the shell with the -ex argument, then the unlock command is not run if the test suite fails and make returns the error to us and the board is never unlocked. A simple fix might be:

lock $MY_DEVELOPMENT_BOARD
if make test TARGET=$MY_DEVELOPMENT_BOARD
then
    unlock $MY_DEVELOPMENT_BOARD
else
    unlock $MY_DEVELOPMENT_BOARD
    false
fi

If you favour compactness (and only having to type out the unlock command once) then perhaps:

lock $MY_DEVELOPMENT_BOARD
make test TARGET=$MY_DEVELOPMENT_BOARD && res=$? || res=$?
unlock $MY_DEVELOPMENT_BOARD
[ 0 -ne "$res" ] && false

However what about the following. Note that the “unlock” command is similar to a  a “finally” operation in some languages but will be executed before the catch statement:

lock $MY_DEVELOPMENT_BOARD
try make test TARGET=$MY_DEVELOPMENT_BOARD
unlock $MY_DEVELOPMENT_BOARD
catch echo "System tests failed! Please see logs"

The above can readily be implemented aided by a couple of simple shell functions:

try () {
        if [ -z $exception_has_been_thrown ]
        then
                "$@" || exception_has_been_thrown=1
        fi
}

catch () {
        if [ ! -z $exception_has_been_thrown ]
        then
                "$@"
                false   # If "sh -ex" then exit at this point
                unset exception_has_been_thrown
        fi
}

These scripts don’t make a big difference for the simple script above. However what if you are running multiple test suites sequentially under lock?

lock $MY_DEVELOPMENT_BOARD
try make smoke_test TARGET=$MY_DEVELOPMENT_BOARD
try make heavy_regression_test TARGET=$MY_DEVELOPMENT_BOARD
unlock $MY_DEVELOPMENT_BOARD
catch echo "System tests failed! Please see logs"

Now at last the benefits of these wrapper functions really make sense. Because all the test suites are run using the try function then they will be skipped if previous try blocks have reported an error. This gives us behaviour similar to -e but delays the reporting of the error until the unlock has been performed.

To close, and for the historians amoung you, despite my starting this post with a reference to 1977 the code presented wouldn’t have run in the original Bourne shell because it uses features that were added later. However I think that by 1986 (SVR3) then all the features used here would have been available. Certainly if you know different then please let me know… I’d be interested.

Share