Skip to content
AgileLogics
5 min read

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

  1. Reproduce the problem with the smallest input you can.
  2. Trace it with set -x to see what really runs.
  3. Question assumptions — "that variable is set", "that folder exists", "that command succeeded".
  4. Fix it, and add a check (a guard clause or a test) so it can't happen silently again.