Debugging Scripts
Prerequisites: Functions, Error handling in scripts
When a script misbehaves, the fastest way to understand it is to watch exactly what it runs.
Tracing with set -x
set -x prints each command — after expansion — before running it:
name="my file.txt"
set -x
touch "$name"
count=$(ls | wc -l)
set +x
echo "count is $count"
+ touch 'my file.txt'
++ ls
++ wc -l
+ count=1
+ set +x
count is 1
Lines starting with + show what actually ran, with variables replaced
by their values and quoting made visible — ideal for spotting a variable
that's empty or split unexpectedly. Nested commands get extra + signs.
Trace a whole script without editing it: bash -x script.sh.
Better trace lines
Set PS4 to include the line number and function:
PS4='+ line ${LINENO}: '
set -x
total=$(( 2 + 3 ))
set +x
+ line 3: total=5
+ line 4: set +x
Other techniques
bash -n script.sh— check syntax without running anything.- ShellCheck — catches most bugs before you run the script at all.
- Print values at key points:
echo "DEBUG: file=[$file]" >&2. The brackets make empty values and stray spaces visible. - Check exit codes:
echo "status: $?"right after a command. - Test pieces in isolation — paste a single command into the terminal with sample values.
printf 'echo "unterminated\n' > broken.sh
bash -n broken.sh
echo "syntax check exit code: $?"
broken.sh: line 1: unexpected EOF while looking for matching `"'
syntax check exit code: 2
A debugging process
- Reproduce the problem with the smallest input you can.
- Trace it with
set -xto see what really runs. - Question assumptions — "that variable is set", "that folder exists", "that command succeeded".
- Fix it, and add a check (a guard clause or a test) so it can't happen silently again.