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.