The set -euo pipefail Pattern
"Strict mode" is the community name for starting a script with set -euo pipefail, three flags combined to make bash behave closer to how most programmers instinctively expect a scripting language to behave: stop on error, stop on undefined variables, and don't hide errors inside pipelines. -e (errexit) exits the script the moment any command returns non-zero, -u (nounset) turns any reference to an unset variable into a fatal error instead of a silent empty string, and -o pipefail makes a pipeline's exit status reflect the first failing stage instead of only the last command. None of these are bash's default behavior, which is why scripts written without them can run for years hiding bugs that only strict mode would have caught immediately.
Cricket analogy: Strict mode is like playing under DRS with every decision reviewable, instead of the old system where an umpire's single incorrect call stood uncorrected for the rest of the innings.
How errexit (-e) Actually Behaves
-e has notoriously inconsistent edge cases that trip up even experienced scripters: it does not trigger inside a condition tested by if, while, or until; it does not trigger for any command that is part of a && or || chain except the last one; and it does not trigger for commands whose exit status is captured, like x=$(false), unless that assignment is itself checked. This means grep pattern file || true deliberately suppresses errexit for that one command (a common idiom for 'this command is allowed to fail'), while some_function failing inside an if some_function; then is expected and does not stop the script, since that's precisely what the condition is testing.
Cricket analogy: It's like a no-ball rule that only voids the delivery when the bowler oversteps on their front foot, but not when a fielder's boundary-rope contact happens during a completely separate phase of play — the exception is scoped precisely.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# errexit does NOT fire here: failure is the condition being tested
if grep -q 'ERROR' app.log; then
echo "errors found"
fi
# Explicitly allow a command to fail without killing the script
rm -f /tmp/stale.lock || true
# nounset catches this typo immediately instead of silently using an empty string
# echo "$USRE_HOME" # would exit: USRE_HOME: unbound variable
# pipefail ensures a failing first stage is not masked by a successful grep
curl -sf https://api.example.com/status | grep -q '"ok":true'nounset, pipefail, and IFS Hardening
-u (nounset) is the cheapest bug-catcher in strict mode: a mistyped variable name like $USRE_HOME instead of $USER_HOME normally expands to an empty string and silently corrupts downstream logic, but under -u bash exits immediately with 'unbound variable'. The one caveat is that arrays and positional parameters need care — $@ and $* are safe, but referencing $1 when no arguments were passed will trigger nounset, so scripts often guard with ${1:-} when an argument is genuinely optional. Many strict-mode scripts also set IFS=$'\n\t' to remove the space from bash's default word-splitting characters, which prevents accidental splitting of filenames containing spaces during unquoted loops, though the real fix is still to quote expansions properly rather than rely on IFS alone.
Cricket analogy: nounset catching a typo'd variable is like a strict scorer who refuses to log a run for a batter not officially in the lineup, immediately flagging the discrepancy instead of quietly crediting the wrong player.
When an optional positional parameter is legitimate, guard it explicitly: verbose="${1:-false}" avoids nounset failures while still documenting the default clearly, rather than disabling -u for the whole script.
Strict mode is not a silver bullet: -e is silently ignored inside command substitutions used in certain contexts, and functions called as the last command in an &&/|| chain do not trigger errexit even on failure. Always pair strict mode with explicit checks on commands whose failure truly matters, rather than assuming three flags make the script bulletproof.
- Strict mode is
set -euo pipefail: errexit on failure, nounset on undefined variables, pipefail on masked pipeline errors. - -e does not trigger inside if/while/until conditions or for non-final commands in && / || chains — these are intentional exemptions.
- -u turns typo'd or undefined variable references into immediate fatal errors instead of silent empty-string corruption.
- Guard genuinely optional parameters with ${1:-default} rather than disabling nounset for the whole script.
- -o pipefail makes a pipeline's exit status reflect the first failing stage, not just the final command.
- Setting IFS=$'\n\t' reduces accidental word-splitting on spaces but is not a substitute for quoting expansions.
- Strict mode reduces an entire class of silent bugs but is not exhaustive — explicit checks are still needed for commands whose failure matters most.
Practice what you learned
1. Which three flags together make up bash 'strict mode'?
2. Why does errexit NOT stop the script when a command fails inside an `if` condition?
3. Under `set -u`, what happens when a script references `$1` but no arguments were passed to it?
4. What problem does `set -o pipefail` specifically solve?
5. What is the correct idiom to allow a specific command to fail without stopping the script under strict mode?
Was this page helpful?
You May Also Like
Defensive Bash Scripting
Techniques for writing bash scripts that fail safely, validate their inputs, and avoid the common footguns that turn a small bug into a production incident.
Bash Script Debugging Techniques
Practical techniques for finding and fixing bugs in bash scripts, from tracing execution with set -x to using ShellCheck and traps for structured diagnostics.
Writing Portable Shell Scripts
How to write shell scripts that behave consistently across bash versions, macOS vs Linux, and POSIX sh, avoiding the common traps that break scripts on a different machine.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics