Exit Codes & Error Handling
Exit statuses, set -euo pipefail, trap, and building Bash scripts that fail loudly and safely instead of silently continuing after an error.
Exit Codes: The Universal Success/Failure Signal
Every command returns a numeric exit status when it finishes: 0 means success, any non-zero value (1–255) means failure, with specific meanings sometimes assigned by convention.
grep "pattern" file.txt
echo $? # 0 if found, 1 if not found, 2 if file.txt doesn't exist
ls /nonexistent
echo $? # 2 (or another non-zero value depending on the error)
exit 0 # explicitly exit a script successfully
exit 1 # explicitly exit a script with a generic failure
set -euo pipefail: The Production Standard
#!/usr/bin/env bash
set -euo pipefail
| Flag | Effect |
|---|---|
-e | Exit immediately if any command fails (returns non-zero) |
-u | Treat referencing an undefined variable as an error |
-o pipefail | A pipeline’s exit status is the LAST non-zero exit among all stages, not just the final command |
# Without pipefail, this pipeline reports SUCCESS even though grep found nothing to match:
false | echo "done" # exit status: 0 (only echo's status counts)
set -o pipefail
false | echo "done" # exit status: 1 (the pipeline correctly reports the failure)
Where set -e Doesn’t Save You
set -e
# set -e does NOT trigger inside an if condition — this is intentional, checking failure is the point
if grep "ERROR" app.log; then
echo "found errors"
fi
# set -e is ALSO bypassed for commands combined with && or ||
grep "ERROR" app.log && echo "found" || echo "not found"
# and it does NOT propagate through a function's return value the way you might expect in every bash version — always check explicitly for critical steps
trap: Running Code on Exit, Error, or Signal
cleanup() {
echo "Cleaning up temporary files..."
rm -rf "$TMPDIR"
}
trap cleanup EXIT # runs on ANY script exit — success, failure, or Ctrl+C
trap 'echo "Error on line $LINENO"' ERR # runs whenever a command fails (with set -e)
trap 'echo "Interrupted"; exit 1' INT # runs on Ctrl+C (SIGINT)
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"; echo "Cleaned up $TMPDIR"' EXIT
echo "Working in $TMPDIR..."
# ... script logic that might fail ...
Custom Error Handling With Meaningful Exit Codes
#!/usr/bin/env bash
set -euo pipefail
E_MISSING_ARG=10
E_FILE_NOT_FOUND=11
E_DEPLOY_FAILED=12
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <config_file>" >&2
exit "$E_MISSING_ARG"
fi
CONFIG="$1"
if [ ! -f "$CONFIG" ]; then
echo "Error: config file not found: $CONFIG" >&2
exit "$E_FILE_NOT_FOUND"
fi
./deploy_app.sh "$CONFIG" || {
echo "Deployment failed" >&2
exit "$E_DEPLOY_FAILED"
}
Distinct exit codes let calling scripts, CI pipelines, or monitoring systems distinguish why a script failed, not just that it did.
Checking Command Success Explicitly
if ! command -v docker &> /dev/null; then
echo "docker is not installed" >&2
exit 1
fi
curl -sf https://api.example.com/health || {
echo "Health check failed" >&2
exit 1
}
Production Considerations
set -euo pipefailshould be the default first line of every non-trivial production script — treat its absence as a code review flag.- Use
trap ... EXITfor cleanup logic (temp files, lock files) so it runs reliably even when the script exits early due to an error or Ctrl+C. - Assign distinct, documented exit codes for different failure modes in scripts that other tooling (CI, monitoring, orchestration) needs to react to differently.
Quick Interview Answer
“Every command returns a 0 (success) or non-zero (failure) exit code, accessible via
$?.set -euo pipefailis the standard defensive header for production scripts:-eexits on any failed command,-ucatches undefined variable references, andpipefailmakes a pipeline’s exit status reflect ANY failed stage, not just the last one.trap cleanup EXITguarantees cleanup logic runs even if the script errors out partway through.”
Common Mistakes
- Writing production scripts without
set -euo pipefail, letting a failed command silently continue into the next step. - Not knowing about
pipefail, and being surprised a pipeline “succeeds” even when an early stage clearly failed. - Skipping
trap ... EXITcleanup, leaving stale temp files or lock files behind after a script errors out.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form