Shell bugs are often silent; tracing and static analysis find them in minutes.
Bash has built-in tools for seeing what a script really does:
bash -n script.shparses the script without running anything: a quick syntax check.bash -x script.sh, orset -xinside the script, prints every command after expansion, prefixed with+. Turn it off again withset +xto trace just one section.- Customise the prefix with
PS4:PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}: 'adds file, line and function to each traced line. Use single quotes so it expands at trace time. trap '...' ERRcan report the failing command ($BASH_COMMAND) and line ($LINENO).set -o functraceandset -o errtracemake DEBUG and ERR traps apply inside functions.
ShellCheck is a static analyser that knows hundreds of shell pitfalls: unquoted variables (SC2086), for f in $(ls), useless cat, cd without error handling, unreachable code, variables used but never assigned, and bashisms in #!/bin/sh scripts. Every warning has an ID with a wiki page explaining it. Run it in your editor and in CI; disable a specific check on one line with a # shellcheck disable=SC2086 comment when you are sure.
For harder problems: print values to stderr with declare -p var, and reproduce the issue in a small test script before touching the real one.
Example
bash -n deploy.sh # syntax check only, runs nothing
bash -x deploy.sh arg1 # trace every command
# inside a script:
PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x
total=$(( 2 + 3 ))
# + deploy.sh:6: total=5
set +x
trap 'echo "error at line $LINENO: $BASH_COMMAND" >&2' ERR
declare -p total >&2 # declare -- total="5"
shellcheck deploy.sh
# In deploy.sh line 3:
# rm -rf $dir/*
# ^--^ SC2086 (info): Double quote to prevent globbing and word splitting.Remember
- bash -n checks syntax, set -x traces execution
- A PS4 with ${LINENO} makes traces readable
- Run ShellCheck on every script, in the editor and in CI
Watch out for
- Leaving
set -xon in a script that handles secrets: they are printed in the trace.