A gotcha with the Bourne shell's set -e and &&

July 7, 2010

Suppose that you have the following Bourne shell code:

set -e
cmd1 && cmd2 && cmd3
echo all done

Now suppose that cmd2 exits with a non-zero status. Do you expect the script to abort, or to print out 'all done'?

My assumption when I was writing a script recently was that the script would abort; after all, I had set -e turned on. But this is not what happens, and in fact most Bourne shell manpages spell it out explicitly; set -e only exits if what fails is a simple command that is not having its exit status tested. Everything except the final command in a series of &&'s is having its exit status tested, so failure of cmd2 here merely causes cmd3 not to be run.

(Different Bourne shell implementations use different wording about the exact conditions, but I suspect that they behave the same. See the Single Unix Specification description of set for perhaps the authoritative wording on it.)

If you are writing shell scripts, the immediate consequence of this is that it is not entirely safe to start out writing a script with various coded error checks and then later decide that you always want things to just exit on errors and add a set -e to handle it all; you may find that your script is not aborting when you want it to, or alternately that the script's failure to abort is quietly hiding the fact that a single command did fail and that something else didn't get run.


Comments on this page:

From 78.86.151.9 at 2010-07-07 19:35:36:

In bash, you can get the expected behaviour by doing "set -o pipefail"

David

By Dan.Astoorian at 2010-07-08 10:06:59:

set -e would be almost useless if it didn't allow conditionals to be evaluated without aborting the script. I don't think one would expect an abort from:

if command; then
   echo true
else
   echo false
fi

if command returned a non-zero status. Short-circuit && and || operators are a fairly common idiom as a compact form of if statements (and in fact you're using that idiom in your example), so the shell's behaviour makes perfect sense to me.

About a year ago, though, I found an interesting bug in one of my scripts; basically, I had done:

#!/bin/sh -e
NOW=`date +%s`
SECS=`expr $NOW % 60`
#[...]

This script worked about 98.3% of the time. However, if it was run at zero seconds after the minute, the expr evaluated to zero, and expr returns 1 if its expression evaluates to zero, so the script would exit.

(By the way, I believe the previous comment about set -o pipefail is not quite accurate: it would make cmd1 | cmd2 abort the script if cmd1 failed, but not cmd1 && cmd2.)

--Dan

Written on 07 July 2010.
« How zpool status reports on ZFS scrubs and resilvers
Unix programs should avoid exiting non-zero for clever reasons »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Jul 7 18:45:56 2010
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.