set -e
terminates the script if a nonzero exit code is encountered, except under certain conditions. To sum up the dangers of its usage in a few words: it doesn't behave how people think it does.
In my opinion, it should be regarded as a dangerous hack which continues to exist for compatibility purposes only. The set -e
statement does not turn shell from a language that uses error codes into a language that uses exception-like control flow, it merely poorly attempts to emulate that behaviour.
Greg Wooledge has a lot to say on the dangers of set -e
:
In the second link, there are various examples of the unintuitive and unpredictable behaviour of set -e
.
Some examples of the unintuitive behaviour of set -e
(some taken from the wiki link above):
set -e
x=0
let x++
echo "x is $x"
The above will cause the shell script to prematurely exit, because let x++
returns 0, which is treated by the let
keyword as a falsy value and turned into a nonzero exit code. set -e
notices this, and silently terminates the script.
set -e
[ -d /opt/foo ] && echo "Warning: foo is already installed. Will overwrite." >&2
echo "Installing foo..."
The above works as expected, printing a warning if /opt/foo
exists already.
set -e
check_previous_install() {
[ -d /opt/foo ] && echo "Warning: foo is already installed. Will overwrite." >&2
}
check_previous_install
echo "Installing foo..."
The above, despite the only difference being that a single line has been refactored into a function, will terminate if /opt/foo
does not exist. This is because the fact that it worked originally is a special exception to set -e
's behaviour. When a && b
returns nonzero, it is ignored by set -e
. However, now that it's a function, the exit code of the function is equal to the exit code of that command, and the function returning nonzero will silently terminate the script.
set -e
IFS=$'\n' read -d '' -r -a config_vars < config
The above will read the array config_vars
from the file config
. As the author might intend, it terminates with an error if config
is missing. As the author might not intend, it silently terminates if config
does not end in a newline. If set -e
were not used here, then config_vars
would contain all lines of the file whether or not it ended in a newline.
Users of Sublime Text (and other text editors which handle newlines incorrectly), beware.
set -e
should_audit_user() {
local group groups="$(groups "$1")"
for group in $groups; do
if [ "$group" = audit ]; then return 0; fi
done
return 1
}
if should_audit_user "$user"; then
logger 'Blah'
fi
The author here might reasonably expect that if for some reason the user $user
does not exist, then the groups
command will fail and the script will terminate instead of letting the user perform some task unaudited. However, in this case the set -e
termination never takes effect. If $user
cannot be found for some reason, instead of terminating the script, the should_audit_user
function will just return incorrect data as if set -e
was not in effect.
This applies to any function invoked from the condition part of an if
statement, no matter how deeply nested, no matter where it is defined, no matter even if you run set -e
inside it again. Using if
at any point completely disables the effect of set -e
until the condition block is fully executed. If the author is not aware of this pitfall, or does not know their entire call stack in all possible situations in which a function can be called, then they will write buggy code and the false sense of security provided by set -e
will be at least partially to blame.
Even if the author is fully aware of this pitfall, the workaround is to write code in the same way as one would write it without set -e
, effectively rendering that switch less than useless; not only does the author have to write manual error handling code as if set -e
were not in effect, but the presence of set -e
may have fooled them into thinking that they do not have to.
Some further drawbacks of set -e
:
- It encourages sloppy code. Error handlers are completely forgotten about, in the hopes that whatever failed will report the error in some sensible way. However, with examples like
let x++
above, this is not the case. If the script dies unexpectedly, it is usually silently, which hinders debugging. If the script does not die and you expected it to (see previous bullet point), then you have a more subtle and insidious bug on your hands.
- It leads people into a false sense of security. See again the
if
-condition bullet point.
- The places where the shell terminates are not consistent between shells or shell versions. It is possible to accidentally write a script which behaves differently on an older version of bash due to the behaviour of
set -e
having been tweaked between those versions.
set -e
is a contentious issue, and some people aware of the issues surrounding it recommend against it, while some others just recommend taking care while it is active to know the pitfalls. There are many shell scripting newbies who recommend set -e
on all scripts as a catch-all for error conditions, but in real life it does not work that way.
set -e
is no substitute for education.