Skip to content
C.W.K.
Stream
Lesson 09 of 12 · published

Error Handling: set -euo pipefail

~13 min · set, errexit, pipefail, nounset

Level 0Window Tourist
0 XP0/95 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

The defensive header

#!/usr/bin/env bash
set -u
set -o pipefail

Shell options are design choices, not a professionalism badge. -u and pipefail can expose mistakes; -e has context-dependent exceptions and must be tested against the script's control flow. Add only the options whose behavior the script understands.

What each flag does

  • -e (errexit) — exit immediately when any command returns non-zero (with carve-outs for tests and pipelines).
  • -u (nounset) — exit when an undefined variable is referenced. Catches typos like $PROOJECT.
  • -o pipefail — pipeline exit code is the first non-zero stage's, not just the last command's.
  • IFS — controls splitting in specific expansion and read contexts. Quote expansions and set IFS locally where a data format requires it instead of changing it globally by ritual.

Carve-outs you'll need

set -e doesn't fire if the command is part of an if, a && chain, or piped into |. Use that to your advantage:

# OK — caller checks the exit code
if grep -q ERROR build.log; then ...

# Optional cleanup with a narrow, validated target
cache_dir="${project_root:?}/.cache"
if [[ -d "$cache_dir" ]]; then
  rm -r -- "$cache_dir"
fi

Default with -u

With -u, referencing an unset variable explodes. The ${var:-} form gives an empty default, ${var:-fallback} gives a real one. Always defensive-default user-supplied env vars: "${DEBUG:-0}".

Custom error reporting

err() { echo "$0:${LINENO}: $*" >&2; exit 1; }
[[ -d "$dir" ]] || err "missing $dir"

A small err helper makes script failures actionable. Pair with trap (next lesson) for cleanup on the way out.

errexit is not exception handling

Commands used as tests, parts of AND/OR lists, and some pipeline or subshell contexts interact with set -e differently. Check expected failures explicitly and test each important branch instead of assuming every nonzero status aborts.

Inspect which pipeline stage failed

pipefail changes the aggregate status but does not explain the broken stage. Capture diagnostics or split critical pipelines so recovery can name the producer or consumer that failed.

Code

Defensive bash header·bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

err() { echo "${0}:${LINENO}: $*" >&2; exit 1; }

: "${INPUT:?INPUT env var required}"
[[ -f "$INPUT" ]] || err "$INPUT does not exist"

wc -l < "$INPUT"

External links

Exercise

Take any existing script, add set -euo pipefail at the top, run it. If it now fails, that's a real bug surfacing. Fix one (likely a typo or a pipeline that was eating an error).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.