Saturday, March 14, 2009

Bash: Use "if" instead of "&&"

Using the –e option in a bash script can be quite useful. It causes the shell to stop processing and exit whenever a command results in an error. There are many situations where continuing processing after an error has occurred can be detrimental and result in a loss of data. But if you use the '&&' conditional a lot in your scripts, it can also cause them to exit unexpectedly.
So, it's a little trick to use the '&&' conditional instead of an 'if' block. For example:
[[ -n $var ]] && {
   echo
   echo $var
   echo
}

It's really just syntactic sugar, because it looks cooler (in a way) than:
if [[ -n $var ]]; then
   echo
   echo $var
   echo
fi

Usually, the result is exactly the same, because these two things are logically the same thing. But let's say you are writing a larger script that does a lot of work. If it's doing anything important, and there is a potential for lost data if there is an error in your script, you may decide to set the '-e' option in your script. This will stop the script if any of the commands in the script has an error. In some cases, continuing processing when there is an error can do a lot of damage, so the '-e' option can be good practice for critical scripts.

But if you use the '&&' conditional a lot, you could be surprised when the '-e' option causes your script to exit when it shouldn't!

Here's a trivial example. Let's say we have a 'while' loop that reads some lines of input and does something special for empty lines:
echo $'one\ntwo\nthree' | while read input; do
    echo "input: $input"
    if [[ -z $input ]]; then
        echo "empty $input"
        # do some special processing for an empty line...
    fi
done

# Do a bunch more stuff...
echo "Continuing processing..."

This works fine. But let's change it to use the '&&' operator instead of an 'if' block:
echo $'one\ntwo\nthree' | while read input; do
    echo "input: $input"
    [[ -z $input ]] && {
        echo "empty $input"
        # do some special processing for an empty line...
    }
done 

# Do a bunch more stuff...
echo "Continuing processing..."

This will fail! And it's only because we have the '-e' option set! The reason is that the 'while' command uses the exit status of the last command it ran. In this particular case, the last command was the '[[' command, which failed because the last line of input was not empty.

This also means that if the '[[' command is the last command in your script, the script itself may exit with an error status when there is actually nothing wrong.

Using the 'if' command is safer, because it wraps the '[[' command, uses that exit status for the conditional check, but returns its own successful exit status. So the '-e' option will not cause an 'if' to fail the whole script.

No comments: