Why you need it
By default bash does not stop on errors. It keeps running the script as if nothing happened:
#!/usr/bin/env bash
cp /no/such/file /tmp/dest
▸error, but the script keeps going
rm -rf /tmp/dest/*
▸now this deletes SOMETHING ELSE
Without strict mode a script like this corrupts data silently. Strict mode is three flags that must sit at the top of any non-trivial script:
set -euo pipefail
What each flag does
| Flag | Long name | Effect |
|---|---|---|
-e | errexit | Stop at the first command with exit code != 0 |
-u | nounset | Stop if you use a variable that was never set |
-o pipefail | pipefail | A pipe counts as failed if any stage failed |
Without pipefail, the command cmd1 | cmd2 takes its exit code from the
last command in the pipe. So false | true returns 0 (success), because
bash looks only at true. With pipefail, bash sees that false failed and
the pipe fails too.
The idiom
The standard prelude of every production script:
#!/usr/bin/env bash
set -euo pipefail
# your code goes here
Sometimes you add -x for debugging (it traces every command to stderr):
set -euxo pipefail # with tracing
Or you turn it on temporarily:
set -x
some_buggy_command
set +x # turn it off
When to disable it locally
Sometimes -e gets in the way: a command may return != 0 on purpose, and that
is fine. Then use || true or an explicit check:
# diff returns 1 when the files differ. For us that is not an error
diff a.txt b.txt > result.txt || true
# Alternative: switch off -e for a single command
if some_check; then
echo ok
else
echo "check failed but we're fine with that"
fi
With -u there is a similar trap. ${VAR:-default} gives a default value
when the variable is not set, without stopping:
PORT=${PORT:-8080} # 8080 if PORT is not setecho "${OPTIONAL:-}" # empty string instead of an errorAlternatives and fine points
set -E-trap ERRhandlers are inherited inside functions (off by default)set -o functrace- the same for DEBUG/RETURN trapsshopt -s inherit_errexit--eis inherited inside command substitution$(...)(also off by default)
The full defensive prelude you see in production:
#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit # bash 4.4+
trap 'echo "ERROR at line $LINENO" >&2' ERR