⚠️ Extended Error Handling
Robust scripts anticipate failures and respond gracefully. This section covers advanced error handling patterns.
🧭 Exit Codes Recap
Every command returns 0 (success) or 1-255 (failure):
| ls /exists # Returns 0
ls /missing # Returns 2
|
Capture with $?:
| ls /missing
code=$?
if [ $code -ne 0 ]; then
echo "Command failed with code $code"
fi
|
🧪 set -e (Exit on Error)
Automatically exit when any command fails:
| #!/bin/bash
set -e
echo "Step 1"
false # Script exits here
echo "Step 2" # Never reached
|
Common pitfalls:
| set -e
grep pattern file || true # ✅ Prevents exit
grep pattern file # ❌ Would exit if no match
|
Commands in conditionals don't trigger exit:
| set -e
if false; then
echo "Won't print"
fi
echo "This prints" # ✅ Because false was in if condition
|
🧠 set -u (Exit on Undefined Variable)
Catch typos in variable names:
| set -u
echo "$UNDEFINED_VAR" # Exits with error
|
Provide defaults to avoid:
| set -u
: "${VAR:=default}"
echo "$VAR" # Safe
|
🧪 set -o pipefail
By default, pipeline exit code is the last command:
| set +e # Disable exit-on-error for demo
false | true # Exit code: 0 (misleading!)
true | false # Exit code: 1
|
With pipefail, exit code is the first failing command:
| set -o pipefail
false | true # Exit code: 1 (correct!)
|
Recommended combination:
This is the "strict mode" used in production scripts.
🧠 Custom Error Handlers
Trap ERR
Run code whenever any command fails:
| err_handler() {
echo "Error on line $1" >&2
echo "Last command: $BASH_COMMAND" >&2
}
trap 'err_handler $LINENO' ERR
false # Triggers handler
ls /missing # Triggers handler
|
Trap EXIT
Cleanup on any exit (success or failure):
| cleanup() {
rm -f /tmp/mytempfile
echo "Cleaned up temporary files"
}
trap cleanup EXIT
# Even if script fails, cleanup runs
|
🧪 Error Codes Pattern
Define custom error codes:
1
2
3
4
5
6
7
8
9
10
11
12
13 | readonly E_OK=0
readonly E_MISSING_CONFIG=1
readonly E_NETWORK_FAILURE=2
readonly E_PERMISSION_DENIED=3
check_config() {
[ -f config.yml ] || return $E_MISSING_CONFIG
}
check_config || {
echo "Configuration error (code $?)" >&2
exit $E_MISSING_CONFIG
}
|
This makes error handling explicit and maintainable.
🧠 Defensive Programming Patterns
| if [ $# -lt 2 ]; then
echo "Usage: $0 <input> <output>" >&2
exit 1
fi
if [ ! -f "$1" ]; then
echo "Input file not found: $1" >&2
exit 2
fi
|
Fail Fast with Meaningful Messages
| deploy() {
: "${ENVIRONMENT:?Environment not set}"
: "${VERSION:?Version not set}"
echo "Deploying $VERSION to $ENVIRONMENT"
# ... deployment logic
}
|
Check Command Prerequisites
| require_command() {
command -v "$1" >/dev/null 2>&1 || {
echo "Required command not found: $1" >&2
exit 1
}
}
require_command curl
require_command jq
require_command git
|
🧪 Logging Errors
Centralize error reporting:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | log_error() {
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}
log_warning() {
echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}
log_info() {
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*"
}
# Usage
if ! mkdir -p "$TARGET_DIR"; then
log_error "Failed to create directory: $TARGET_DIR"
exit 1
fi
|
🧾 Summary
- Use
set -euo pipefail for strict error handling.
- Trap
ERR and EXIT for cleanup and logging.
- Define custom error codes for clarity.
- Validate inputs before processing.
- Log errors with timestamps for debugging.
- Always provide meaningful error messages.
👉 Continue to: Traps and Signals