📜 Part of Pranav Kulkarni's technical portfolio Visit pranavkulkarni.org →
Lesson 5 · Shell Scripting

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

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

Running Debug Mode

$ bash -x script.sh # Debug entire script
$ bash -n script.sh # Syntax check only

Error Handling

error_handler() {
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 pipefail so failures in earlier commands aren’t hidden.
  • Expected non-zero: use || true or handle via if blocks.

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

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; }

✅ Practice (20 minutes)

  • Enable set -x and customize PS4 to 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.