Bash Error Handling and Debugging
A script that runs without error handling is fragile. If one command fails silently, the next command may run on bad data and produce unexpected results. Proper error handling makes scripts reliable, predictable, and easy to troubleshoot.
Exit Status Codes
Every command in Bash returns an exit status (also called exit code) when it finishes. The special variable $? stores this code.
┌──────────────────────────────────────────────────┐ │ Exit Status Meaning │ │ │ │ 0 → Command succeeded │ │ 1 → General error │ │ 2 → Misuse of shell command │ │ 126 → Command found but not executable │ │ 127 → Command not found │ │ 128+ → Command terminated by a signal │ └──────────────────────────────────────────────────┘
#!/bin/bash ls /existing_folder echo "Exit code: $?" # prints 0 ls /nonexistent_folder echo "Exit code: $?" # prints 2 or similar non-zero
Checking Exit Status After a Command
#!/bin/bash cp source.txt destination.txt if [ $? -ne 0 ]; then echo "ERROR: Copy failed." exit 1 fi echo "Copy successful."
set -e – Exit on Error
Adding set -e at the top of a script tells Bash to stop immediately if any command fails. Without this, Bash continues running even after an error.
#!/bin/bash set -e echo "Step 1: Starting" cp nonexistent.txt /tmp/ # This will fail echo "Step 2: This will NOT run because -e stops the script"
set -u – Exit on Undefined Variable
set -u causes Bash to exit with an error if an undefined variable is used. This catches typos in variable names.
#!/bin/bash set -u username="Alice" echo "Hello, $usernme" # Typo: usernme instead of username # Script stops with error: unbound variable
set -o pipefail – Catch Errors in Pipes
Normally, the exit status of a pipeline is the exit status of the last command only. With set -o pipefail, the pipeline fails if any command in it fails.
#!/bin/bash set -o pipefail cat nonexistent.txt | wc -l # cat fails, pipefail catches it echo "Exit: $?"
Using All Three Together (Best Practice)
#!/bin/bash set -euo pipefail
This single line activates all three safety options and is the recommended starting point for any production Bash script.
trap – Run Cleanup on Exit or Error
The trap command runs a specified command when the script exits or encounters a specific signal. This is ideal for cleanup tasks like deleting temporary files.
Cleanup on Exit
#!/bin/bash tmpfile=$(mktemp) trap "rm -f $tmpfile; echo 'Cleaned up temp file.'" EXIT echo "Working with $tmpfile" echo "Some data" > $tmpfile cat $tmpfile # When script exits (normally or due to error), trap runs cleanup
Trap Signals
| Signal | Meaning |
|---|---|
| EXIT | Script finishes (any reason) |
| ERR | Any command returns non-zero exit code |
| INT | User presses Ctrl+C |
| TERM | Script receives a kill signal |
Trap on ERR
#!/bin/bash set -e trap 'echo "ERROR occurred on line $LINENO"' ERR echo "Step 1" ls /nonexistent # triggers ERR trap echo "Step 2" # not reached
Output:
Step 1 ERROR occurred on line 5
Custom Error Messages with a Function
#!/bin/bash
error_exit() {
echo "ERROR: $1" >&2 # Print to stderr
exit 1
}
if [ ! -f "config.txt" ]; then
error_exit "config.txt not found. Cannot continue."
fi
Debugging with set -x
set -x enables debug mode. Bash prints each command before executing it, prefixed with +. This helps trace exactly what the script is doing.
#!/bin/bash set -x name="Alice" echo "Hello, $name"
Output:
+ name=Alice + echo 'Hello, Alice' Hello, Alice
Enable Debugging for Part of the Script Only
#!/bin/bash echo "Normal mode" set -x # Debug this block only name="Bob" echo "Debugging: $name" set +x echo "Back to normal mode"
Debugging Flow Diagram
┌──────────────────────────────────────────────────────┐ │ Debugging Tools Summary │ │ │ │ set -e → Stop on first error │ │ set -u → Stop on undefined variable │ │ set -x → Print each command before running │ │ set -o pipefail→ Catch errors inside pipes │ │ trap ERR → Run cleanup code on any error │ │ $? → Check last command exit code │ │ echo to stderr→ Send error messages to stderr │ └──────────────────────────────────────────────────────┘
Logging Errors to a File
#!/bin/bash
LOGFILE="script.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOGFILE
}
log "Script started"
if ! ping -c 1 google.com &>/dev/null; then
log "ERROR: No network connection"
exit 1
fi
log "Network check passed"
log "Script completed"
bash -n – Check Script Syntax Without Running
bash -n myscript.sh
This checks the script for syntax errors without actually running any commands. A good practice before running any new script.
Key Takeaways
- Always check
$?after important commands to detect failures. - Use
set -euo pipefailat the top of every script for robust error handling. - Use
trapto run cleanup code when the script exits or errors occur. - Use
set -xto print each command during execution for debugging. - Use
bash -n script.shto check syntax errors before running. - Send error messages to stderr using
echo "error" >&2so they appear separately from normal output.
