Notes & Tools

Bash strict mode, revisited

The classic Bash “strict mode” boilerplate everyone copies:

set -euo pipefail
IFS=$'\n\t'

I’ve been using this for years and it mostly does what I want. But each of those flags has a sharp edge I keep stepping on.

set -e doesn’t propagate into && chains the way you’d hope. cmd1 && cmd2 where cmd1 fails will not abort the script — set -e triggers only on unchecked exit codes. If that’s news to you, you have at least one bug.

set -u is the one I love most. ${MY_VAR:?missing} is even better when the variable should exist: it stops the script with a message instead of just “unbound variable: MY_VAR” with no line number.

set -o pipefail is the one I forget to enable and then debug for 30 minutes. Without it, failing_command | tee log.txt returns 0 because tee succeeded. With it, the pipeline returns the first non-zero exit code.

Two additions I’ve adopted in 2025:

shopt -s inherit_errexit nullglob

inherit_errexit makes $(subshell) honor set -e properly — without it, out=$(failing-cmd) does not abort. nullglob makes for f in *.log produce an empty loop when there are no .log files, instead of looping with literal *.log.

This is incrementally more strict every year and I’m fine with it.