Bash Script Auto-Exit on Error with set -e Guide
Learn how to make Bash scripts automatically exit with code 1 on any command failure using set -e and strict mode set -euo pipefail. Avoid manual $? checks for reliable error handling in bash scripts, pipelines, and DevOps automation.
How to make a Bash shell script automatically exit with code 1 if any command returns a non-zero exit status, without explicitly checking each command?
I have a Bash script that runs multiple commands. I want it to abort immediately and exit with return value 1 upon any command failure, avoiding manual checks like this:
dosomething1
if [[ $? -ne 0 ]]; then
exit 1
fi
dosomething2
if [[ $? -ne 0 ]]; then
exit 1
fi
What is the best way to achieve this behavior in Bash?
Use set -e right after your shebang in the Bash script—it forces an immediate exit with the failing command’s exit code (usually 1 for errors) whenever any simple command fails, skipping those tedious $? checks entirely. For even tighter control, go with the full strict mode: set -euo pipefail, which also bombs out on unset variables and pipeline glitches. This approach keeps your Bash scripts lean, reliable, and fail-fast, just like you’d expect from modern scripting.
Contents
- Introduction to Error Handling in Bash Scripts
- Using set -e for Automatic Exit
- Strict Mode: set -euo pipefail
- Common Pitfalls and Exceptions
- Alternatives: Traps and Custom Functions
- Practical Examples
- Best Practices
- Sources
- Conclusion
Introduction to Error Handling in Bash Scripts
Ever had a Bash script chug along after a command tanks, only to spew garbage or worse, pretend everything’s fine? That’s the default behavior—Bash ignores non-zero exit codes unless you babysit with if [[ $? -ne 0 ]]; then exit 1; fi after every line. Annoying, right? But flipping that script on its head is simple and built-in.
The goal here: make your script abort instantly on any failure, exiting with code 1 (or whatever the bad command spat out). No more manual checks cluttering your code. This is core Bash error handling, and it’s a game-changer for automation, DevOps pipelines, or any Linux workflow where reliability matters.
Using set -e for Automatic Exit
Here’s the trick everyone overlooks at first: set -e, also known as errexit. Slap it at the top of your script, and poof—any command returning non-zero triggers an instant exit with that exact code.
#!/bin/bash
set -e
# These will run fine
echo "Starting up"
ls /tmp
# This fails? Script exits here with ls's code (2 for no such file/dir)
ls /nonexistent
echo "This never prints"
Run it: bash myscript.sh; echo $? spits out 2. Clean. You can even invoke via bash -e myscript.sh without editing the file.
Why does this work? The Bash manual spells it out: pipelines, lists, or simple commands exit the shell on non-zero status. But it’s not perfect—more on gotchas later. For now, ditch the boilerplate checks; set -e handles 80% of cases.
Strict Mode: set -euo pipefail
set -e alone? Solid starter. But pair it with friends for bulletproof Bash scripts: set -euo pipefail. Breaks down like this:
-e: Exit on any non-zero command (errexit).-u: Treat unset variables as errors—no more silentecho $foobombs.-o pipefail: Pipelines fail if any stage fails, not just the last one.
Full shebang:
#!/bin/bash
set -euo pipefail
Test a pipeline: false | true without pipefail returns 0 (success!). With it? Exits 1. Perfect for grep | awk chains in real scripts.
As SS64 docs note, this combo is the gold standard—no extra cost, just safer code. Pro tip: Add -x for debug traces if you’re troubleshooting (set -euxo pipefail).
Common Pitfalls and Exceptions
But wait—set -e isn’t magic. It skips errors in certain spots, per the Bash FAQ. Here’s what trips folks up:
- Conditionals and loops:
if false; then echo nope; fiorwhile false; do ...; done—the test itself doesn’t trigger exit.
set -e
if ls /nonexistent; then echo "Won't happen"; fi
echo "But this prints anyway"
-
Pipelines (pre-pipefail):
cmd1 | cmd2ignores early fails. -
Arithmetic:
((i=0; i++))orlet i++can quietly fail. -
Assignments that look like them:
local var=$(badcmd)sweeps errors under the rug. -
&&/|| lists: Only the final chain matters.
Workarounds? Use || true to ignore deliberately, or strict mode covers most. And remember: subshells inherit it separately.
Frustrating at first, but knowing these keeps scripts predictable.
Alternatives: Traps and Custom Functions
Hate the exceptions? Roll your own with traps or functions. Traps shine for logging before exit.
From Intoli’s guide:
set -e
trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
trap 'echo "\"${last_command}\" command failed with exit code $?."' EXIT
Now failures print: "ls /fake" command failed with exit code 2. Elegant.
Or a custom checker:
exit_on_error() {
exit_code=$?
last_command="${@:2}"
if [ $exit_code -ne 0 ]; then
echo "\"${last_command}\" failed with ${exit_code}." >&2
exit $exit_code
fi
}
# Usage: ls /fake; exit_on_error $? !!
Trap ERR for inheritance: set -E; trap 'echo Error at line $LINENO' ERR. Flexible when set -e feels too blunt.
Practical Examples
Real-world? Say you’re deploying:
#!/bin/bash
set -euo pipefail
cd /app || { echo "Can't cd to /app"; exit 1; }
git pull
npm install
npm test
systemctl restart myapp
Fails at git pull? Done, exit 1. No partial deploys.
CLI test:
$ bash -e -o pipefail -u <<EOF
echo "Test"
false | true
echo "Nope"
EOF
Exits early.
For loops with checks:
set -e
for dir in /tmp/*; do
[ -d "$dir" ] || continue # Skip non-dirs without exiting
ls "$dir"
done
These snippets scale from quick hacks to production pipelines.
Best Practices
Fail fast, always. Debian packaging mandates set -e for a reason—unhandled errors cascade badly.
- Start every script with
set -euo pipefail. - Log via traps:
trap 'echo "Failed at $BASH_COMMAND" >&2' ERR. - Test edge cases: pipes, arith, conditionals.
- Debug: Temporarily add
-x. - Explicit ignores:
cmd || true. - Exit codes: Use specifics (127 for not found) over generic 1.
Avoid over-relying on set -e in complex logic—mix with explicit checks. As this Stack Overflow thread debates, traps offer nuance without pitfalls.
Your scripts? Bulletproof now.
Sources
- Automatic exit from bash shell script on error — Comprehensive Q&A on set -e, strict mode, and gotchas: https://stackoverflow.com/questions/2870992/automatic-exit-from-bash-shell-script-on-error
- Exit on errors in Bash scripts — Practical traps for error logging and custom exit handlers: https://intoli.com/blog/exit-on-errors-in-bash-scripts/
- The Set Builtin (Bash Manual) — Official documentation on set -e, pipefail, and shell options: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
- What does “set -e” mean in a Bash script? — Discussion of errexit behavior, exceptions, and best practices: https://stackoverflow.com/questions/19622198/what-does-set-e-mean-in-a-bash-script
- set command (Bash) — Quick reference for set options including errexit and pipefail: https://ss64.com/bash/set.html
Conclusion
Stick set -euo pipefail in your Bash scripts, layer on traps for polish, and wave goodbye to manual error babysitting—your code exits cleanly with the right code on any hiccup. Pitfalls exist, but awareness fixes them; the payoff is scripts that just work, every time. Next deployment? You’ll thank yourself when it fails loudly before shipping broken bits.
Put set -e at the top of the script. This will cause the shell to exit immediately if a simple command exits with a nonzero exit value.
Code set -e will immediately terminate your script with an exit status of 1 if any command exits with a non-zero status.
Use Bash’s errexit option. Add set -e (or set -o errexit) at the top of your script. This causes the shell to exit immediately when any command returns a non-zero status.
The simplest way is set -e. It causes Bash to exit immediately upon encountering any command failure. Enhance with set -u and set -o pipefail for comprehensive error handling.
Use set -e at the start of your script to exit immediately on command failure. Combine with set -o pipefail for pipes: #!/usr/bin/env bash set -euo pipefail.
