Guide Bash-Scripting Advanced

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.

4 min read

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
FlagEffect
-eExit immediately if any command fails (returns non-zero)
-uTreat referencing an undefined variable as an error
-o pipefailA 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 pipefail should be the default first line of every non-trivial production script — treat its absence as a code review flag.
  • Use trap ... EXIT for 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 pipefail is the standard defensive header for production scripts: -e exits on any failed command, -u catches undefined variable references, and pipefail makes a pipeline’s exit status reflect ANY failed stage, not just the last one. trap cleanup EXIT guarantees 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 ... EXIT cleanup, 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