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:
#!/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
| Flag | Effect |
|---|---|
-e | Exit immediately if command fails |
-u | Error on unset variables |
-o pipefail | Pipeline 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):
#!/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
#!/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:
#!/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:
#!/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:
#!/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
#!/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
#!/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"
}
What does 'set -u' do?
Quick Reference
| Command | Purpose |
|---|---|
set -e | Exit on error |
set -u | Error on undefined vars |
set -o pipefail | Pipeline error handling |
trap cmd EXIT | Run cmd on exit |
trap cmd INT | Run cmd on Ctrl+C |
set +e / set -e | Disable/enable -e |
Key Takeaways
- Start scripts with
set -euo pipefail - Use
trap cleanup EXITfor guaranteed cleanup - Create reusable
die()andlog()functions - Validate all inputs before using them
- Use
set +ewhen you need to handle errors manually - Test error paths, not just happy paths
Next: working with arrays.