Bash Error Handling - set -e, trap, Exit Codes, Retry Patterns

Learn robust error handling in bash scripts with set -euo pipefail, trap for cleanup, exit code checking, retry logic, and error reporting patterns.

Error Handling

Detailed Explanation

Error Handling in Bash

Bash scripts fail silently by default. Without proper error handling, a failed command can cause subsequent commands to run with incorrect data or in an unexpected state. Robust error handling is essential for production scripts.

set -euo pipefail

The most important line in any bash script:

set -euo pipefail
  • -e (errexit): Exit immediately if any command returns a non-zero exit code
  • -u (nounset): Treat references to unset variables as errors
  • -o pipefail: A pipeline fails if any command in it fails (not just the last)

Trap for Cleanup

The trap command executes code when the script receives a signal or exits:

cleanup() {
  echo "Cleaning up..."
  rm -rf "$TEMP_DIR"
  # Restore original state
}

trap cleanup EXIT        # always run on exit
trap cleanup ERR         # run on error
trap 'echo "Interrupted"; exit 1' INT TERM  # handle Ctrl+C

Checking Exit Codes

# Method 1: if statement
if ! command_that_might_fail; then
  echo "Command failed with exit code $?"
  exit 1
fi

# Method 2: || operator
command_that_might_fail || {
  echo "Command failed"
  exit 1
}

# Method 3: explicit check
command_that_might_fail
status=$?
if [ $status -ne 0 ]; then
  echo "Failed with status $status"
fi

Retry Pattern

retry() {
  local max_attempts="$1"
  local delay="$2"
  shift 2
  local cmd="$@"

  local attempt=1
  while [ $attempt -le $max_attempts ]; do
    if eval "$cmd"; then
      return 0
    fi
    echo "Attempt $attempt/$max_attempts failed. Retrying in ${delay}s..."
    sleep "$delay"
    attempt=$((attempt + 1))
  done

  echo "All $max_attempts attempts failed"
  return 1
}

# Usage
retry 3 5 curl -sf https://api.example.com/health

Error Reporting

# Custom error handler
error() {
  local line="$1"
  local msg="$2"
  echo "ERROR at line $line: $msg" >&2
}

trap 'error $LINENO "Script failed"' ERR

# Detailed error logging
log_error() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a error.log >&2
}

Safe Temporary Files

TEMP_DIR=$(mktemp -d) || { echo "Failed to create temp dir"; exit 1; }
TEMP_FILE=$(mktemp) || { echo "Failed to create temp file"; exit 1; }

trap 'rm -rf "$TEMP_DIR" "$TEMP_FILE"' EXIT

Use Case

Error handling is critical for production deployment scripts, backup automation, CI/CD pipelines, and any script that modifies system state. Without proper error handling, a failed database migration could leave the system in an inconsistent state, or a failed backup could go unnoticed. Trap and cleanup patterns ensure resources are always released.

Try It — Bash Cheat Sheet

Open full tool