Automatic exit from Bash shell script on error
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The standard way to make a Bash script exit automatically on error is set -e. In production scripts, the stronger pattern is set -euo pipefail, which also catches unset variables and hidden pipeline failures. This article covers all three flags, the important exceptions to set -e behavior, explicit error handling patterns, and a production-ready script template you can use as a starting point.
The Basic Tool: set -e
set -e (also known as errexit) tells Bash to exit immediately when a simple command returns a non-zero exit status.
When cp fails because the source file does not exist, the script exits with the same non-zero status. Without set -e, the script would silently continue to Step 3, potentially operating on incomplete data.
Why set -euo pipefail Is the Industry Standard
Most production Bash scripts use all three flags together:
Each flag addresses a different class of silent failure:
| Flag | Name | What It Catches |
-e | errexit | Commands that return non-zero exit status |
-u | nounset | References to undefined variables |
-o pipefail | pipefail | Failures in any command within a pipeline |
Why -u Matters
Without -u, typos in variable names silently expand to empty strings:
With -u, the script exits immediately with an error message identifying the unset variable.
Why pipefail Matters
Without pipefail, a pipeline's exit status is the exit status of the last command only:
The cat failure is masked because head succeeds. With pipefail, the pipeline inherits the non-zero status from cat, and set -e terminates the script.
Important Exceptions to set -e
set -e does not trigger on every non-zero exit status. Understanding the exceptions prevents confusion:
Commands in Conditional Context
Commands used as conditions in if, while, or until statements are allowed to fail:
Commands Before Logical Operators
Commands on the left side of && or || are exempt:
Commands in Subshells and Command Substitution
The behavior in subshells depends on the Bash version and context. In general, set -e does not propagate into command substitutions in all cases:
Complete Exception Reference
| Context | Does set -e Trigger? | Example |
| Simple command | Yes | false exits the script |
if condition | No | if false; then ... continues |
Left side of && | No | false && echo x continues |
Left side of || | No | false || echo x continues |
| Command substitution | Varies by Bash version | $(false) behavior is inconsistent |
| Functions called in conditional | No | if my_func; then ... continues |
Handling Expected Failures Explicitly
When a command might legitimately fail, suppress it explicitly rather than disabling strict mode:
The || true pattern is the most common. It makes the intent clear: this specific command is allowed to fail. Use it sparingly and only when the failure is genuinely harmless.
Add a Trap for Better Debugging
An ERR trap runs a command whenever a non-zero exit status triggers set -e. This is invaluable for diagnosing failures in CI pipelines and automated deployments:
For scripts that need cleanup regardless of success or failure, trap EXIT instead:
The EXIT trap fires on normal exit, error exit, and most signals, making it the right choice for resource cleanup.
A Production-Ready Script Template
This template combines all the patterns discussed into a reusable starting point:
This gives you automatic failure on unexpected errors, safe variable handling, pipeline failure detection, diagnostic output with line numbers, and a clean structure that separates concerns.
Common Pitfalls
- Assuming set -e is universal. It has documented exceptions for conditionals, logical operators, and subshells. Read the Bash manual section on
errexitto understand the edge cases. - Using set -e without pipefail. A failing command early in a pipeline is silently masked by a successful command at the end. Always pair them.
- Overusing || true. Every
|| trueis a place where errors are intentionally ignored. If you find yourself adding it to many lines, the script may need restructuring rather than more exception suppression. - Forgetting -u and getting silent empty-string expansion. Variable typos are one of the most common causes of destructive Bash bugs, like
rm -rf $UNDEFINED/expanding torm -rf /. - Using set -e in library functions sourced by other scripts. If the calling script does not use
set -e, the behavior is inconsistent. Library functions should validate their own preconditions explicitly. - Not testing trap behavior.
ERRtraps do not fire in the same exception contexts whereset -edoes not fire. Test your error handling with deliberate failures during development.
Summary
set -eexits the script on command failure, but has important exceptions for conditionals and logical operators.set -euo pipefailis the standard strict mode for production scripts, catching errors, unset variables, and pipeline failures.- Handle expected failures explicitly with
|| true, conditional checks, or temporaryset +eblocks. - Use
trap ... ERRfor diagnostic output andtrap ... EXITfor cleanup that must run regardless of outcome. - Structure scripts with a
mainfunction,readonlyconstants, and clear prerequisite validation. - Test error handling deliberately. Do not assume strict mode catches everything without verifying the behavior in your specific Bash version.

