How to debug a Bash script
Debug Bash scripts with set -x, bash -n, PS4, trap ERR, and ShellCheck. A practical, copy-paste guide to finding shell bugs fast.
Tldr
To debug a Bash script, run `bash -n script.sh` to catch syntax errors without executing, add `set -x` to trace every command as it runs, set a richer `PS4` for line numbers, install ShellCheck for static analysis, and add `trap 'echo "error on line $LINENO"' ERR` to pinpoint failures.
Intro
Bash gives you several built-in debugging facilities — most people only use set -x and stop there. This guide walks through the full toolbox in the order you should reach for them when something breaks.
Steps
Name
Run a syntax-only parse check
Text
`bash -n` parses the script without running any commands. It is the fastest way to find missing fi/done/esac, mismatched quotes, and other structural errors — safe to run on scripts you do not trust.
Name
Run ShellCheck for static analysis
Text
ShellCheck flags ~300 known bug patterns: quoting issues, unused variables, masked exit codes, portability bugs. Install via brew, apt, or cabal — or paste your script into bashchecker.com.
Name
Trace execution with set -x
Text
Add `set -x` near the top of the script (or pass `-x` on the command line: `bash -x script.sh`). Bash prints every expanded command before running it, prefixed with `+`.
Name
Make trace output useful with PS4
Text
The default `+` prefix tells you nothing. Set PS4 to include script name, line number, and function name so you can navigate large scripts.
Name
Catch errors with trap ERR
Text
Combined with `set -e`, an ERR trap runs whenever any command fails. Print the line number and the failing command for instant context.
Name
Isolate the failure
Text
Once you know which line fails, comment out everything after it and run again. Or wrap suspicious blocks in `( set -x; suspect_block )` to enable tracing for just that section. This converges on the bug fast.
Name
Inspect the environment with declare and env
Text
When a variable is wrong, print it: `declare -p VAR` shows the type, scope, and value. `env | grep PREFIX_` shows exported variables the script inherited.
Faq
Q
How do I debug a script I cannot modify?
A
Run it with `bash -x script.sh` to enable tracing without editing the file. For ShellCheck, just `shellcheck script.sh` — it reads the file, never executes it.
Q
set -x is too verbose — how do I narrow it?
A
Use `set -x` at the start of the suspicious block and `set +x` at the end. Or wrap the block in `( set -x; ... )` to scope tracing to a subshell.
Q
Can I step through a Bash script like a debugger?
A
Yes — `bashdb` is a gdb-style debugger for Bash with breakpoints, step, and watch. Install via your package manager. For most bugs, set -x and trap ERR are faster.