Robust Scripts with Error Handling
In real-world scripting, errors will happen. Files won't exist, commands will fail, networks will be unreachable. A good script handles these situations gracefully instead of silently producing wrong results.
Exit Codes
Every command in Bash returns an exit code (0โ255). By convention: 0 means success, and anything else means failure.
# Check the exit code of the last command
ls /tmp
echo $? # 0 (success)
ls /nonexistent
echo $? # 2 (failure โ no such directory)
# Use exit codes in conditionals
if grep -q "pattern" file.txt; then
echo "Pattern found"
else
echo "Pattern not found"
fi
# Set your own exit code
exit 0 # script succeeded
exit 1 # script failedStrict Mode: set -euo pipefail
The "unofficial Bash strict mode" is a set of options that make your scripts fail early instead of silently continuing after errors.
#!/bin/bash
set -euo pipefail
# set -e โ Exit immediately if any command fails
# set -u โ Treat undefined variables as errors
# set -o pipefail โ A pipeline fails if ANY command in it fails
# Without set -e:
false # exit code 1, but script continues
echo "This still runs"
# With set -e:
false # script exits HERE
echo "This never runs"set -euo pipefail. It catches a huge class of bugs automatically. Add IFS=$'\n\t' for even safer behavior.
The trap Command
trap lets you execute cleanup code when your script exits or receives a signal. It's essential for cleaning up temp files, releasing locks, etc.
#!/bin/bash
# Create a temp file
tmpfile=$(mktemp)
# Clean up when the script exits (for ANY reason)
trap 'rm -f "$tmpfile"; echo "Cleaned up";' EXIT
# Clean up on Ctrl+C
trap 'echo "Interrupted!"; exit 1' INT
# Your script logic...
echo "Working with $tmpfile"
echo "data" > "$tmpfile"
# trap on ERR โ runs on any error (with set -e)
trap 'echo "Error on line $LINENO"' ERRDebugging with bash -x
The -x flag makes Bash print each command before executing it, prefixed with +. It's invaluable for debugging.
# Run entire script in debug mode
bash -x myscript.sh
# Or enable/disable debugging in parts of your script
set -x # Turn on debugging
echo "This will be traced"
some_function
set +x # Turn off debugging
echo "This won't be traced"
# Custom debug output
debug() {
[[ "${DEBUG:-0}" == "1" ]] && echo "[DEBUG] $*" >&2
}
DEBUG=1
debug "Processing file: $filename"Error Messages to stderr
Always write error messages to stderr (&2) so they don't mix with normal output.
# Error function
err() {
echo "[ERROR] $*" >&2
}
warn() {
echo "[WARN] $*" >&2
}
# Usage
if [[ ! -f "$config_file" ]]; then
err "Config file not found: $config_file"
exit 1
fi>&2 for error messages.
Try It Yourself
Summary
You've learned exit codes and $?, the strict mode triplet (set -euo pipefail), how to use trap for cleanup, debugging with bash -x, and the importance of writing errors to stderr. These practices turn fragile scripts into production-quality tools.