One line belongs at the top of nearly every bash script:
set -euo pipefail
It is not magic — it is three independent settings, each changing one way bash silently tolerates failure. Scripts with this line fail early, loudly, and legibly; scripts without it usually fail late and quietly. Here is what each letter buys.
-e: stop on failure #
Without -e, bash runs every line no matter what the last one did — a failed copy followed by a delete of the "successful" backup is a genre of outage. With set -e, any command that fails ends the script, unless the failure is checked:
set -e
cp app.conf out/ # failure here stops the script
if grep -q debug app.conf; then # checked: fine either way
echo "debug on"
fi
cmd || true # explicitly allowed to fail
The if/&&/|| exceptions matter: anything you deliberately test is exempt. See exit codes for the grammar.
-u: no unset variables #
set -u
config="${confi_path}" # typo: bash exits, "unbound variable"
Without -u, an unset variable expands to empty and the script continues with whatever that emptiness means — often rm -rf "$dir/" with an empty $dir. With it, typos in variable names become immediate, named errors. Parameter expansion defaults (${1:-}) remain the way to say "empty is fine".
-o pipefail: the whole pipe must work #
By default, a pipeline's status is its last command's — so curl -sf url | grep x succeeds even when curl failed, and grep happily reports no matches on empty input. pipefail makes the pipeline fail if any stage fails:
set -euo pipefail
curl -sf "$url" | jq -r '.key' # a failed download now stops the script
Combined with -e, a broken upstream stops everything instead of laundering garbage downstream.
The fine print #
| Setting | Catches |
|---|---|
-e | Unchecked command failures |
-u | Unset and misspelled variables |
-o pipefail | Failures hidden inside pipelines |
-E andare not unconditional: grep that "fails" by finding nothing, arithmetic that evaluates to zero, and commands in if conditions all behave differently under -e. The debugging note shows how to trace these with bash -x.
Guards for the failure paths belong to traps; readable failure messages come from exit codes. The scripting series continues.
