Debugging
Debugging Bash scripts with set -x, bashdb-style tracing, shellcheck, and practical techniques for finding the exact line that's failing.
set -x: Trace Every Command as It Runs
#!/usr/bin/env bash
set -x # print each command (with expanded variables) before executing it
APP="myapp"
echo "Deploying $APP"
set +x # turn tracing back off
bash -x deploy.sh # trace an entire script without editing it
bash -x deploy.sh 2>trace.log # capture the trace to a file for later review
Output looks like:
+ APP=myapp
+ echo 'Deploying myapp'
Deploying myapp
The + prefix marks trace lines, distinguishing them from the script’s actual output.
Tracing Only a Specific Section
echo "Before the tricky part"
set -x
complicated_function arg1 arg2
set +x
echo "After the tricky part"
Customizing the Trace Prefix (PS4)
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x deploy.sh
This adds the filename and line number to every trace line — invaluable in larger scripts made of multiple sourced files, where a plain + gives no idea which file/line produced it.
Combining set -x With set -e for Real Debugging
#!/usr/bin/env bash
set -euxo pipefail # -e (exit on error) + -u (undefined var error) + -x (trace) + pipefail
Running with all four flags during development, then dropping -x for production, is a common workflow.
Syntax Checking Without Running
bash -n script.sh # parse only, checks syntax without executing anything
shellcheck: Static Analysis (Catches Real Bugs, Not Just Syntax)
shellcheck script.sh
shellcheck catches things a syntax check never will: unquoted variables, unreachable code, common quoting mistakes, and portability issues between bash and POSIX sh. It should be part of every CI pipeline that ships Bash scripts.
# Example finding from shellcheck:
# SC2086: Double quote to prevent globbing and word splitting.
rm $FILE # flagged
rm "$FILE" # correct
Printing Variables at Key Points
debug() {
[ "${DEBUG:-false}" = "true" ] && echo "[DEBUG] $*" >&2
}
debug "APP_NAME is: $APP_NAME"
debug "Retrying, attempt $RETRY of $MAX_RETRIES"
DEBUG=true ./deploy.sh # toggle debug output without editing the script
Using trap for Error Line Numbers
trap 'echo "Error on line $LINENO: command \"$BASH_COMMAND\" failed" >&2' ERR
set -e
This prints the exact line and command that caused a failure, without needing to run the whole script under -x.
Checking Variable Expansion Interactively
# Quickly test an expansion in an interactive shell before committing it to a script
VAR="test-value"
echo "${VAR^^}" # verify uppercase expansion works as expected before using it
Production Considerations
- Never ship a production script with
set -xpermanently enabled — it can leak secrets (API keys, passwords) passed as arguments or stored in variables directly into logs. - Run
shellcheckin CI for every script change — most real-world Bash bugs (quoting, unset variables, wrong test operators) are caught by it before the script ever runs. - Use
PS4customization with file/line info for any script that sources other files — a bareset -xtrace becomes nearly useless once a script grows beyond one file.
Common Debugging Workflow
bash -n script.sh # 1. syntax check
shellcheck script.sh # 2. static analysis
bash -x script.sh 2>trace.log # 3. full trace if something's still wrong
grep -n "^+" trace.log # 4. review just the traced commands
Quick Interview Answer
“
bash -x script.shtraces every command as it executes, which is the go-to first step for debugging.bash -nchecks syntax without running anything, andshellcheckdoes static analysis, catching real bugs like unquoted variables that a syntax check would miss. CustomizingPS4to include${BASH_SOURCE}:${LINENO}makes traces far more useful in multi-file scripts, andtrap ... ERRwith$LINENOpinpoints exactly where a failure happened without a full trace.”
Common Mistakes
- Leaving
set -xenabled in a production script, potentially leaking secrets into logs. - Skipping
shellcheckentirely and only relying on manual code review, missing common quoting/scoping bugs. - Debugging blind with scattered
echostatements instead of usingset -x/trap ERR, which give more precise, structured information.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form