By default, Bash will happily keep running even if a command fails. This can lead to cascading failures that are difficult to diagnose:
#!/bin/bash
# Without error handling — DANGEROUS
cd /nonexistent_dir # Fails silently
rm -rf * # Now deletes files from the WRONG directory!Professional scripts fail loudly and early, making debugging easy.
Place this near the top of every production script:
#!/usr/bin/env bash
set -euo pipefailThis single line activates three critical safety options:
| Option | Effect |
|---|---|
set -e |
Exit immediately if any command fails (non-zero exit code) |
set -u |
Exit with error if an undefined variable is used |
set -o pipefail |
Pipeline fails if any command in it fails (not just the last) |
#!/usr/bin/env bash
set -euo pipefail
# -e catches this: script exits instead of continuing
cp important.db /backup/ || { echo "Copy failed!"; exit 1; }
# -u catches this: using $UNDEFINED_VAR would exit with an error
echo "$UNSET_VARIABLE" # Error: UNSET_VARIABLE: unbound variable
# -o pipefail catches this:
grep "pattern" nonexistent_file | wc -l # Without pipefail, exits 0💡 Add
IFS=$'\n\t'alongsideset -euo pipefailto prevent word-splitting bugs on whitespace.
set -x prints each command to stderr before executing it, prefixed with +. This is your most powerful debugging tool.
#!/usr/bin/env bash
set -x # Enable trace
name="World"
echo "Hello, $name"
# Output:
# + name=World
# + echo 'Hello, World'
# Hello, World
set +x # Disable trace (useful to reduce noise in loops)Debug only a specific section:
#!/usr/bin/env bash
set -euo pipefail
prepare_data
transform_data
set -x # Start tracing only from here
load_data
set +x # Stop tracing
generate_reportRun a script in debug mode without modifying it:
bash -x ./myscript.sh
bash -euxo pipefail ./myscript.shEvery command in Linux exits with a code from 0 to 255:
0= success1= general error2= misuse of shell builtin126= command found but not executable127= command not found130= script terminated by Ctrl+C
ls /etc/hosts
echo "Exit code: $?" # 0
ls /nonexistent
echo "Exit code: $?" # 2
# Always exit with meaningful codes from your own scripts
validate_input() {
[[ -z "$1" ]] && return 1
[[ "$1" =~ ^[0-9]+$ ]] || return 2
return 0
}
validate_input "" && echo "ok" || echo "Empty input (code: $?)"
validate_input "abc" && echo "ok" || echo "Not a number (code: $?)"
validate_input "42" && echo "ok" || echo "err"Combine set -e with trap ERR to get informative error messages:
#!/usr/bin/env bash
set -euo pipefail
# Error handler
err_handler() {
local exit_code="$?"
local line_number="$1"
echo ""
echo "❌ ERROR: Script failed at line $line_number with exit code $exit_code"
echo " Command: $BASH_COMMAND"
echo " Stack: ${FUNCNAME[*]}"
echo ""
}
trap 'err_handler $LINENO' ERR
# --- Script body ---
echo "Starting..."
cp /nonexistent/file /tmp/ # This will trigger the error handler
echo "This line won't run."Replace bare echo calls with a proper logging system:
#!/usr/bin/env bash
set -euo pipefail
readonly LOG_FILE="/var/log/myscript.log"
readonly SCRIPT_NAME="$(basename "$0")"
log() {
local level="$1"
shift
local message="$*"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
printf "[%s] [%-7s] [%s] %s\n" "$timestamp" "$level" "$SCRIPT_NAME" "$message" | tee -a "$LOG_FILE"
}
log_info() { log "INFO" "$@"; }
log_warning() { log "WARNING" "$@"; }
log_error() { log "ERROR" "$@" >&2; }
log_debug() { [[ "${DEBUG:-}" == "1" ]] && log "DEBUG" "$@" || true; }
# Usage
log_info "Starting backup process..."
log_warning "Disk usage is above 80%"
log_error "Database connection failed"
DEBUG=1 log_debug "Checking variable: value=$some_var"| Technique | Purpose |
|---|---|
set -euo pipefail |
Fail fast: exit on error, undefined vars, pipe failures |
set -x |
Trace mode: print each command before execution |
bash -x script.sh |
Debug without modifying the script |
trap 'handler' ERR |
Catch errors with context (line number, command) |
trap 'cleanup' EXIT |
Always clean up on exit |
Structured log_* functions |
Consistent, timestamped, leveled log messages |
exit N |
Exit with a meaningful code |