A script of mine ran a cd into a directory that didn't exist, shrugged the failure off, and kept going — so the very next line, which was destructive, ran in whatever directory I happened to be standing in instead of the one I'd meant to target. It did its work flawlessly, on entirely the wrong files. The script printed nothing alarming and exited zero. As far as bash was concerned, everything had gone fine.
That's the default that catches everyone eventually: bash, left to itself, treats a failed command as a suggestion. The cd returns non-zero, bash ignores it, and execution marches on as if nothing happened — which is reasonable for an interactive shell where a typo shouldn't kill your session, and quietly dangerous in a script where the next command assumes the last one worked. set -euo pipefail rewrites that contract in one line: stop on the first error, treat an unset variable as an error instead of an empty string, and make a pipeline fail if any stage fails, not only the last. I didn't learn this from a style guide. I learned it cleaning up after a script that obediently ran the right command in the wrong place. The header below goes at the top of everything I write now.
The Template — Add This to Every Script
This is the safest way to start any bash script. Copy this header and build from here. The same strict-mode header is what stops a CI job from passing on a step that actually failed — see bash scripting for CI/CD pipelines.
What you just added
set -euo pipefail combines three safety flags into one line. trap ... ERR tells bash what to print when something fails, including the exact line number. Without these, bash will happily run 50 more lines after an error as if nothing happened.
Adding it to a script, step by step
Step 1 — Create a new script file
Paste the template above, then press Ctrl+X → Y → Enter to save.
Step 2 — Add it to an existing script
Open any script you already have and paste set -euo pipefail and the trap line immediately after #!/bin/bash:
That is all you need. Two lines change every script from "silently broken" to "stops and tells you where it failed."
Step 3 — Make it executable and run it
The script exits immediately on the first failed command and prints the line number. Without an error, it completes normally — you will not see any extra output from the trap.
Step 4 — Test the error trap deliberately
Add a failing command to verify the trap fires:
Run it. You should see:
The second echo never executes. That is set -e working correctly. Remove the test line and build from there.
What Each Flag Does
Each flag is a documented option of the bash set builtin:
| Flag | Long form | What it does |
|---|---|---|
-e | set -o errexit | Stop immediately if any command exits non-zero. Without this, bash ignores failures and keeps running. |
-u | set -o nounset | Treat any unset variable as an error. Without this, $UNDEFINED_VAR silently becomes an empty string — which can cause catastrophic path errors. |
-o pipefail | pipeline failure | A pipeline fails if ANY command in it fails, not just the last one. Without this, false | true succeeds because only true is checked. |
Why This Actually Matters — Real Examples
The dangerous default (no flags)
What happens without -e
The cd fails. Bash ignores it and stays in the current directory. rm -rf * then runs in whatever directory you were already in — potentially deleting everything there. The script prints "Done." You have no idea anything went wrong.
With set -e (safe version)
What happens with -e
The cd fails. The trap fires and prints "Error on line 5." The script stops completely. rm -rf * never runs. Your files are safe.
The undefined variable trap (why -u matters)
Without -u, a typo can delete your entire system
$TARGER_DIR is undefined so bash replaces it with an empty string. The command becomes rm -rf /* — which attempts to delete everything from the root of your filesystem. With set -u, bash would immediately error: "TARGER_DIR: unbound variable" and stop.
Allowing Commands to Fail Intentionally
Sometimes you want a command to fail without stopping the script. There are two clean ways to do this:
Method 1 — Append || true
Method 2 — Use an if statement
The trap Command — Know Exactly What Line Failed
Adding trap gives you a stack trace when things go wrong. Here are the most useful versions:
Quick Reference — All Flags
| Flag | Long form | What it does |
|---|---|---|
| set -e | set -o errexit | Exit immediately on any error |
| set -u | set -o nounset | Error on undefined variables |
| set -o pipefail | — | Pipeline fails if any stage fails |
| set -x | set -o xtrace | Print every command before running it (debug mode) |
| set +e | — | Turn off -e temporarily for a block |
| set +x | — | Turn off debug mode |
Frequently Asked Questions
What does set -euo pipefail do in bash?
set -e exits on any error. set -u treats undefined variables as errors instead of silently using empty strings. set -o pipefail makes pipelines fail if any stage fails rather than only checking the last command. Together they make bash scripts safe and predictable.
Why do bash scripts keep running after an error?
By default, bash ignores non-zero exit codes and continues executing. This is a design decision for interactive use — a typo in a shell session shouldn't kill your terminal. But in scripts it's dangerous. set -e changes this behavior so scripts stop on the first failure.
What is the difference between set -e and set -o errexit?
They are identical. set -e is shorthand for set -o errexit. Both cause immediate exit on command failure. The short form is more common in scripts.
How do I handle errors in a bash script properly?
Add set -euo pipefail right after #!/bin/bash. Add trap 'echo Error on line $LINENO' ERR for visibility. For commands that are allowed to fail, append || true or wrap them in if statements.