Error Handling

Scripts that ignore errors cause more problems than they solve. Let's write defensive code.

The set Command

Enable strict mode at the top of your scripts:

hljs bash
#!/bin/bash
set -e          # Exit on error
set -u          # Error on undefined variables
set -o pipefail # Pipeline fails if any command fails

# Or combine them:
set -euo pipefail
FlagEffect
-eExit immediately if command fails
-uError on unset variables
-o pipefailPipeline returns error if any part fails

Start Every Script This Way

set -euo pipefail catches most common bugs. Add it to every script as a habit.

The trap Command

Run cleanup code when the script exits (normally or on error):

hljs bash
#!/bin/bash
set -euo pipefail

cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/myapp.lock
}

trap cleanup EXIT

# Your script here
echo "Working..."
# Even if script crashes, cleanup runs

Trap Signals

hljs bash
#!/bin/bash

trap 'echo "Caught SIGINT"' INT      # Ctrl+C
trap 'echo "Caught SIGTERM"' TERM    # kill command
trap 'echo "Script exiting"' EXIT    # Any exit

Error Functions

Create reusable error handling:

hljs bash
#!/bin/bash
set -euo pipefail

die() {
    echo "ERROR: $1" >&2
    exit "${2:-1}"
}

warn() {
    echo "WARNING: $1" >&2
}

# Usage
[[ -f "$config_file" ]] || die "Config file not found: $config_file"
[[ -n "$API_KEY" ]] || die "API_KEY environment variable not set"

Check Command Results

Sometimes you need to continue despite errors:

hljs bash
#!/bin/bash
set -euo pipefail

# This won't exit on error
if ! output=$(some_command 2>&1); then
    echo "Command failed with: $output"
    # Handle the error
else
    echo "Success: $output"
fi

Or temporarily disable set -e:

hljs bash
#!/bin/bash
set -euo pipefail

# Risky operation that might fail
set +e  # Disable exit on error
risky_command
result=$?
set -e  # Re-enable

if [[ $result -ne 0 ]]; then
    echo "Risky command failed, but we're handling it"
fi

Comprehensive Error Template

hljs bash
#!/bin/bash
#
# Production-ready script template
#

set -euo pipefail

# Configuration
readonly SCRIPT_NAME="${0##*/}"
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
readonly LOG_FILE="/var/log/${SCRIPT_NAME}.log"

# Logging
log() {
    local level="$1"
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}

info() { log "INFO" "$@"; }
warn() { log "WARN" "$@"; }
error() { log "ERROR" "$@"; }

# Error handling
die() {
    error "$1"
    exit "${2:-1}"
}

# Cleanup
cleanup() {
    local exit_code=$?

    if [[ $exit_code -ne 0 ]]; then
        error "Script failed with exit code: $exit_code"
    fi

    # Cleanup actions
    rm -f /tmp/"${SCRIPT_NAME}".lock

    info "Cleanup complete"
}

trap cleanup EXIT

# Requirement checks
require_root() {
    [[ $EUID -eq 0 ]] || die "This script must be run as root"
}

require_command() {
    command -v "$1" &>/dev/null || die "Required command not found: $1"
}

require_var() {
    [[ -n "${!1:-}" ]] || die "Required environment variable not set: $1"
}

# Main
main() {
    info "Starting $SCRIPT_NAME"

    require_root
    require_command "curl"
    require_command "jq"
    require_var "API_KEY"

    info "All requirements met"

    # Your script logic here

    info "Completed successfully"
}

main "$@"

Validate Input

hljs bash
#!/bin/bash
set -euo pipefail

validate_file() {
    local file="$1"

    [[ -n "$file" ]] || die "No file specified"
    [[ -f "$file" ]] || die "Not a file: $file"
    [[ -r "$file" ]] || die "Cannot read: $file"
}

validate_directory() {
    local dir="$1"

    [[ -n "$dir" ]] || die "No directory specified"
    [[ -d "$dir" ]] || die "Not a directory: $dir"
    [[ -w "$dir" ]] || die "Cannot write to: $dir"
}

validate_number() {
    local num="$1"
    local name="$2"

    [[ "$num" =~ ^[0-9]+$ ]] || die "$name must be a number"
}
Knowledge Check

What does 'set -u' do?

Quick Reference

CommandPurpose
set -eExit on error
set -uError on undefined vars
set -o pipefailPipeline error handling
trap cmd EXITRun cmd on exit
trap cmd INTRun cmd on Ctrl+C
set +e / set -eDisable/enable -e

Key Takeaways

  • Start scripts with set -euo pipefail
  • Use trap cleanup EXIT for guaranteed cleanup
  • Create reusable die() and log() functions
  • Validate all inputs before using them
  • Use set +e when you need to handle errors manually
  • Test error paths, not just happy paths

Next: working with arrays.