Shell Error Handling
Patterns for robust error handling, cleanup, and debugging in shell scripts.
Exit Codes
Standard Exit Codes
| Code |
Meaning |
| 0 |
Success |
| 1 |
General error |
| 2 |
Misuse of shell command |
| 126 |
Command not executable |
| 127 |
Command not found |
| 128+N |
Fatal signal N |
| 130 |
Ctrl+C (SIGINT) |
Checking Exit Status
# Check last command's exit status
if ! command; then
echo "Command failed with status $?" >&2
exit 1
fi
# Alternative pattern
command || {
echo "Command failed" >&2
exit 1
}
# Capture exit status
command
status=$?
if (( status != 0 )); then
echo "Failed with status $status" >&2
fi
Trap for Cleanup
Basic Cleanup Pattern
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
local exit_code=$?
# Remove temporary files
rm -f "$TEMP_FILE" 2>/dev/null || true
exit "$exit_code"
}
trap cleanup EXIT
TEMP_FILE=$(mktemp)
# Script continues...
# cleanup runs automatically on exit
Handling Multiple Signals
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
echo "Cleaning up..." >&2
rm -rf "$WORK_DIR" 2>/dev/null || true
}
handle_interrupt() {
echo "Interrupted by user" >&2
cleanup
exit 130
}
trap cleanup EXIT
trap handle_interrupt INT TERM
WORK_DIR=$(mktemp -d)
Trap Best Practices
# Preserve original exit code in cleanup
cleanup() {
local exit_code=$?
# Cleanup operations here
rm -f "$temp_file" 2>/dev/null || true
# Restore exit code
exit "$exit_code"
}
# Use || true for optional cleanup
trap 'rm -f "$temp_file" 2>/dev/null || true' EXIT
Error Reporting
Standard Error Output
# Always write errors to stderr
echo "Error: Something went wrong" >&2
# Error function
error() {
echo "Error: $*" >&2
}
# Die function - error and exit
die() {
echo "Fatal: $*" >&2
exit 1
}
# Usage
[[ -f "$config" ]] || die "Config file not found: $config"
Verbose Logging
#!/usr/bin/env bash
set -euo pipefail
VERBOSE="${VERBOSE:-false}"
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
fi
}
error() {
echo "[ERROR] $*" >&2
}
log "Starting script"
log "Processing file: $file"
Defensive Programming
Check Prerequisites
# Check required commands exist
require_command() {
command -v "$1" >/dev/null 2>&1 || {
echo "Error: Required command '$1' not found" >&2
exit 1
}
}
require_command jq
require_command curl
require_command shellcheck
Validate Input
# Validate arguments
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <source> <destination>" >&2
exit 1
fi
source_file="$1"
dest_dir="$2"
# Validate file exists
[[ -f "$source_file" ]] || {
echo "Error: Source file not found: $source_file" >&2
exit 1
}
# Validate directory
[[ -d "$dest_dir" ]] || {
echo "Error: Destination directory not found: $dest_dir" >&2
exit 1
}
Safe Temporary Files
# Create secure temp file
TEMP_FILE=$(mktemp) || {
echo "Error: Failed to create temp file" >&2
exit 1
}
# Create secure temp directory
TEMP_DIR=$(mktemp -d) || {
echo "Error: Failed to create temp directory" >&2
exit 1
}
# Always clean up
trap 'rm -rf "$TEMP_FILE" "$TEMP_DIR" 2>/dev/null || true' EXIT
Debugging
Debug Mode
#!/usr/bin/env bash
# Enable debug mode via environment variable
if [[ "${DEBUG:-}" == "1" ]]; then
set -x
fi
set -euo pipefail
# Or toggle with a flag
while getopts "d" opt; do
case $opt in
d) set -x ;;
*) echo "Usage: $0 [-d]" >&2; exit 1 ;;
esac
done
Trace Execution
# Enable tracing for specific section
set -x
problematic_code
set +x
# Trace with custom PS4
export PS4='+ ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
Error Recovery Patterns
Retry Pattern
retry() {
local max_attempts="${1:-3}"
local delay="${2:-1}"
shift 2
local cmd=("$@")
local attempt=1
while (( attempt <= max_attempts )); do
if "${cmd[@]}"; then
return 0
fi
echo "Attempt $attempt failed, retrying in ${delay}s..." >&2
sleep "$delay"
(( attempt++ ))
done
echo "All $max_attempts attempts failed" >&2
return 1
}
# Usage
retry 3 5 curl -f "http://example.com/api"
Fallback Pattern
# Try primary, fall back to secondary
get_config() {
if [[ -f "$HOME/.config/myapp/config" ]]; then
cat "$HOME/.config/myapp/config"
elif [[ -f "/etc/myapp/config" ]]; then
cat "/etc/myapp/config"
else
echo "Error: No config file found" >&2
return 1
fi
}