Saturday, February 25, 2012

Two Simple Tricks for Shell Error Handling

Here are two simple tricks that are easy to use and will make your scripts much more robust.

    Turn on -e mode (do you feel lucky - punk?)

    In this mode any command your script runs which returns a non-zero exitcode - an error in the world of shell - will cause your script to itself terminate immediately with an error.

    You can do that in your shebang line:

    #!/bin/sh -e

    Or using set:

    set -e

    Yes, this is what you want. A neat predictable failure is infinitely better than a noisy unreliable failure.

    If you REALLY want to ignore an error, be explicit about it:

    # I don't care if evil-broken-command fails
    evil-broken-command || true

    Oh and as long as you're messing with shell modes, -e goes well with -x (which I like to think of as shell X-ray).

    Like this:

    #!/bin/sh -ex

    Or like this:

    # turn -x on if DEBUG is set to a non-empty string
    [ -n "$DEBUG" ] && set -x

    That way you can actually see what your script was doing right before it failed.

    Use trap for robust clean-ups

A trap is a snippet of code that the shell executes when it exits or receives a signal. For example, pressing CTRL-C in the terminal where the script is running generates the INT signal. killing the process by default generates a TERM (I.e., terminate) signal.

I find traps most useful for making sure my scripts clean-up after themselves whatever happens (e.g., a non-zero error code in -e mode).

    For example:


    #!/bin/sh -e

    TMPFILE=$(tempfile)
    trap 'echo "removing $TMPFILE"; rm -f $TMPFILE' INT TERM EXIT

    echo TMPFILE=$TMPFILE
    echo hello world > $TMPFILE
    cat $TMPFILE

    # gives user a chance to press CTRL-C
    sleep 3

    # false always returns an error
    false

    echo "NEVER REACHED"

    Note that you can only set one trap per signal. If you set a new trap you're implicitly disabling the old one. You can also disable a trap by specifying - as the argument, like this:

    trap - INT TERM EXIT

1 comment:

  1. # if you're using bash, you can use $BASHPID in place of $$
    for sig in INT TERM EXIT; do
    trap "rm -f \"\$TMPFILE\"; [[ $sig == EXIT ]] || kill -$sig $$" $sig
    done

    ReplyDelete