Control and functions
Enable safer failure behavior
Use unset-variable and pipeline checks while understanding that automatic exit is not a substitute for error handling.
10 minute lesson
By default, Bash keeps going. A typo expands to an empty string, a failing pipeline reports success, and a broken command in the middle of a script just scrolls past. Bash defaults can hide failures. Two settings tighten this up: set -u rejects unset variables, and set -o pipefail makes a pipeline reflect failing stages.
Start a script with explicit policy:
#!/usr/bin/env bash
set -u
set -o pipefail
What set -u catches
Create an unset variable in a disposable script and watch:
target=/var/www
rm -rf "$targt/cache"
# bash: targt: unbound variable
Without -u, the misspelled $targt expands to nothing and the command becomes rm -rf /cache — a different directory entirely. With -u, the script stops at the typo. This one behavior pays for itself for years.
Legitimately optional variables still work: write ${1:-} or ${DEBUG:-0} to supply an explicit default.
What pipefail catches
A pipeline normally reports only the status of its last command:
cat /nonexistent/data.txt | wc -l
# cat: /nonexistent/data.txt: No such file or directory
# 0
printf '%s\n' "$?"
# 0 without pipefail — wc succeeded, so cat's failure vanished
# 1 with pipefail — the failing stage comes through
With set -o pipefail, a failing pipeline returns the status of the last command that failed. Create a failing pipeline like this one in your disposable script and confirm the difference yourself.
What about set -e?
You will see the trio written as set -euo pipefail. The -e part exits the script when a command fails, and it sounds perfect. Treat set -e carefully, though: its exceptions depend on command context. A failure inside an if condition, on either side of && or ||, or in a negated command does not trigger an exit — and that surprises people constantly.
My advice: use set -u and set -o pipefail everywhere. Add -e as a safety net if you like it, but don’t call it error handling. Explicit if checks are clearer around recovery paths:
if ! cp nginx.conf /etc/nginx/nginx.conf; then
printf '%s\n' 'config copy failed, keeping the old file' >&2
exit 1
fi
Add deliberate checks around failures you expect. Automatic exit can’t decide what should happen next — you can.
Lesson completed