Why Every Bash Script Should Start With `set -euo pipefail`
Discover how the single line `set -euo pipefail` transforms fragile Bash scripts into reliable tools, with a practical example, trade‑offs, and quick checks.
18 Sept 2026, 14:19 UTC

When a Bash script silently swallows a failure, you’re usually in trouble
Most production deployments write their automation in Bash. The default behaviour of Bash is permissive: a non‑zero exit code from a command does not stop the script, an unset variable expands to an empty string, and only the last command in a pipeline matters. That permissiveness is convenient for quick experiments but becomes a nightmare when a script runs in CI, a cron job, or a production server. A single unnoticed error can lead to corrupted data, failed services, or security holes.
The concise safety net: set -euo pipefail
Adding the following line at the very top of a Bash script changes the language’s default behaviour in three powerful ways:
- -e – Exit immediately if any command returns a non‑zero status (unless that status is explicitly ignored).
- -u – Treat references to unset variables as errors, causing an immediate exit.
- -o pipefail – In a pipeline, the exit status is that of the rightmost command that failed, not just the last one.
When combined, these options make scripts fail fast, expose hidden bugs, and force the developer to think about error handling instead of relying on default permissive behaviour.
How it works in practice
Below is a minimal script that demonstrates each flag. The script is intentionally simple to keep the focus on the behaviour.
# /usr/bin/env bash
set -euo pipefail
# 1. Normal command – succeeds
echo "Hello, world!"
# 2. Command that fails – script exits immediately
ls /nonexistent/directory
# 3. This line is never reached because the script has already exited
# echo "This will not run"
Running the script will print “Hello, world!” and then exit with status 2 after the ls error. The final echo is never executed, making the failure obvious to the developer or CI system.
Pipeline error propagation
Consider a pipeline where the first command fails but the last succeeds. Without -o pipefail, Bash would treat the pipeline as successful because only the last command’s exit status matters.
set -euo pipefail
# The first command fails (exit 1), but the last succeeds (exit 0)
false | cat
# The script exits after the pipeline because pipefail propagates the failure.
Without -o pipefail, the script would continue, potentially masking a critical error.
Common pitfalls and how to avoid them
While set -euo pipefail is a boon, it can break scripts that were written with Bash’s permissive defaults in mind. Below are a few patterns that need special handling:
- Commands that intentionally return non‑zero – Wrap them with
|| trueor anifblock to prevent-efrom aborting.if ! grep -q "needle" file.txt; then echo "Not found, but that’s fine" fi - Unset variables that may legitimately be empty – Quote all variable expansions to avoid word splitting and use
${VAR:-default}to provide fallbacks.echo "User: ${USER:-unknown}" - Pipelines with expected failures – Use
{ … } || trueto suppresspipefailfor that specific pipeline.{ grep -q "foo" /nonexistent/file; } || true
Testing that the safety net is active
It’s good practice to verify that the script behaves as expected before deploying it. A quick way is to run the script with set -x to trace execution and check the exit status after a deliberately failing command.
#!/usr/bin/env bash
set -euxo pipefail
# This will succeed
true
# This will fail and cause an immediate exit
false
# The following line will never execute
true
When you run this script, you should see the trace output stop after the false command, and the script should exit with status 1.
Trade‑offs and when to skip it
Because set -e aborts on any non‑zero exit, scripts that rely on Bash’s default behaviour for control structures (e.g., if, while, for) may need adjustments. The -e flag is not respected inside if conditions or &&/|| lists, but it can still cause unexpected exits if a command is not guarded. Additionally, legacy scripts that use unset variables for optional parameters will break under -u. In those cases, you can selectively disable the options with set +euo pipefail around the problematic sections, but this defeats the purpose of a uniform safety policy.
Actionable take‑away
1. Add set -euo pipefail as the first line of every new Bash script.
2. Quote all variable expansions and provide defaults with ${VAR:-default}.
3. Guard any intentionally failing command with || true or an if block.
4. Test the script in a controlled environment; use set -x to trace execution.
5. For legacy scripts, gradually migrate by adding the options and refactoring the offending lines.
By making the shell fail fast and exposing hidden errors, set -euo pipefail turns fragile Bash scripts into reliable, maintainable automation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.