Shell scripts: Perils of “set -e” for error handling

By David Röthlisberger. Comments welcome at david@rothlis.net.

Published 12 May 2013. Last updated 7 Dec 2013. This article is Creative Commons licensed.

I used to be a fan of “set -e” in shell scripts. It causes the script to exit immediately if an error occurs, except where you explicitly handle the error with:

some_command_that_might_fail || true  # to ignore error, or:
some_command_that_might_fail || fallback_to_this_other_command

Recently I’ve had a lot of trouble with “set -e”. For example this prints “a”, as you’d expect:

set -e
myfun() { printf a; false; printf b; }
myfun
printf c

…because the “set -e” terminates the whole script immediately after running the “false” command.

But this script prints “a-b-True”, where some people (myself included) might have expected it to print “a-False”:

set -e
myfun() { printf a-; false; printf b-; }
if myfun; then
    printf True
else
    printf False
fi

The “set -e” is ignored completely. Putting another “set -e” inside the definition of myfun doesn’t make any difference.

The posix standard explains this behaviour:

When this [set -e] option is on, if a simple command fails for any of the reasons listed in Consequences of Shell Errors or returns an exit status value >0, and is not part of the compound list following a while, until, or if keyword, and is not a part of an AND or OR list, and is not a pipeline preceded by the ! reserved word, then the shell shall immediately exit.

Bash experts seem to agree: “never rely on set -e” (gnu.bash.bug, BashFAQ). The recommendation is to do your own error checking by stringing together a series of commands with “&&” like this:

mkdir abc &&
cd abc &&
do_something_else &&
last_thing ||
{ echo error >&2; exit 1; }

A few important notes: You don’t need a trailing backslash. You don’t indent the following line. You must stick to 80 chars per line so that everyone can see the “&&” or “||” at the end. If you need to mix ANDs and ORs, group with { these braces } which don’t spawn a sub-shell.