Debugging Shell Scripts
Learn debugging techniques using set -x, traps, and error handling.
Debug Mode
Debugging Bash is mostly about making the script “show its work”: print commands, print variables, and fail at the first real error.
#!/bin/bash
set -x # Print each command before executing
set -e # Exit on error
set -u # Error on undefined variables
set -o pipefail # Catch pipe errors
set -x # Print each command before executing
set -e # Exit on error
set -u # Error on undefined variables
set -o pipefail # Catch pipe errors
For more context in set -x output, customize PS4 (file/line/function).
# Example PS4 (adds file:line + function)
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]}: '
set -x
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]}: '
set -x
Running Debug Mode
$ bash -x script.sh # Debug entire script
$ bash -n script.sh # Syntax check only
$ bash -n script.sh # Syntax check only
Error Handling
error_handler() {
echo "Error on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR
echo "Error on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR
Common “gotchas” with set -e
set -e is useful, but it has edge cases (especially with conditionals and pipelines).
Prefer set -euo pipefail and handle expected failures explicitly.
- •Pipelines: use
set -o pipefailso failures in earlier commands aren’t hidden. - •Expected non-zero: use
|| trueor handle viaifblocks.
Inspect pipeline failures (PIPESTATUS)
If a pipeline fails, $? may not tell you which command failed. Use ${PIPESTATUS[@]} (Bash) to inspect each stage’s exit code.
grep "ERROR" app.log | head -n 5
echo "${PIPESTATUS[@]}" # exit codes of each pipeline command
echo "${PIPESTATUS[@]}" # exit codes of each pipeline command
Static analysis: ShellCheck
ShellCheck catches quoting issues, undefined variables, and common bugs before you run the script.
$ shellcheck script.sh
Logging helpers (make debugging easy)
log() { echo "[INFO] $*" >&2; }
warn() { echo "[WARN] $*" >&2; }
die() { echo "[ERROR] $*" >&2; exit 1; }
warn() { echo "[WARN] $*" >&2; }
die() { echo "[ERROR] $*" >&2; exit 1; }
✅ Practice (20 minutes)
- Enable
set -xand customizePS4to include file + line number. - Create a failing pipeline and inspect
${PIPESTATUS[@]}to locate the failing command. - Run ShellCheck on one of your scripts and fix the top 3 warnings.