Linux Basic — bash -x Flag
bash -x Flag
set -x works from inside a script. Sometimes you don’t want to edit the script — maybe it’s not yours, or you only need one debug run. bash -x script.sh traces from the outside.
The Three Debug Flags
| Invocation | Effect |
|---|---|
bash -x script.sh |
trace every command as it runs |
bash -v script.sh |
print each line as it’s read (before expansion) |
bash -n script.sh |
parse only — don’t execute |
# Trace what actually ran
bash -x deploy.sh
# Syntax check before committing
bash -n deploy.sh && echo "syntax OK"
# Print the script lines verbatim while running (great for showing what's about to happen)
bash -v deploy.sh
Combine them: bash -xv shows both the source line and the expanded trace.
bash -n — The Free Sanity Check
Add this to your editor save hook or CI pipeline:
bash -n myscript.sh
If there’s a syntax error, you get a clean diagnostic without running anything destructive. Costs nothing — should be automatic for every commit.
$ cat broken.sh
if [[ "$x" == "y" ]
echo "missing bracket"
fi
$ bash -n broken.sh
broken.sh: line 1: syntax error near unexpected token `then'
broken.sh: line 1: `if [[ "$x" == "y" ]'
Pass Arguments Through
bash -x ./mytool.sh arg1 arg2
Args after the script name go to the script as $1, $2, etc. Same as a normal invocation, just with tracing prepended.
Capture the Trace to a File
bash -x ./script.sh 2> trace.log
Trace goes to stderr, so 2> redirects only the trace. > (stdout) is left for the script’s normal output:
bash -x ./script.sh > result.txt 2> trace.log
Or merge:
bash -x ./script.sh > combined.log 2>&1
Combine With Strict Mode
bash -euxo pipefail ./script.sh
Adds strict mode + trace to a script that didn’t have them. Useful when debugging a third-party script:
-eexit on error-uexit on unset var-xtrace-o pipefailcatch pipeline failures
When bash -x Doesn’t Help
bash -x traces commands but doesn’t show:
- The expansion of conditionals: you see the
[[ … ]]test but not the per-arg evaluation - The internal logic of functions called from your script (only their invocations)
- What
awk/sed/ Python interpreters are doing internally
For those, you need either set -x inside the function/subshell, or the relevant tool’s own debug mode.
The “Don’t Run, Just Show” Trick
Sometimes you want to see what would run without actually running. There’s no built-in dry-run mode, but you can prefix every external command with echo:
# In your script:
DRY=${DRY_RUN:+echo}
$DRY cp src.txt dst.txt
$DRY rm -rf /tmp/old
Run with DRY_RUN=1 ./script.sh and you see exactly what would happen.
For a more thorough simulation, run with bash -x and a set +e:
bash -ex script.sh # shows trace and aborts at first error
Tracing a Sourced Script
If your code does . lib.sh to load helpers, bash -x traces those too:
$ bash -x main.sh
+ . ./lib.sh
+ die ...
+ printf ...
+ ...
+ main "$@"
+ ...
This is essential for debugging library functions that have somehow gone wrong only when called from main.sh.
bash -i and bash -l
Two other flags worth knowing (not strictly debugging, but in the same family):
| Flag | Effect |
|---|---|
bash -i |
interactive mode — runs ~/.bashrc |
bash -l |
login shell — runs ~/.bash_profile |
Useful when you need to debug “the script works interactively but not from cron” — invoke as cron would: bash -c './script.sh' (no -i, no -l, minimal env).
A Real Workflow
# 1. Syntax check
bash -n deploy.sh || { echo "Syntax error"; exit 1; }
# 2. Static analysis (next page)
shellcheck deploy.sh
# 3. Dry-run with trace, captured
DRY_RUN=1 bash -x deploy.sh 2> trace.log
# 4. Inspect what would have happened
less trace.log
# 5. Real run
./deploy.sh
Five steps; each fast. The first three catch most bugs before they happen.
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
Editing the script to add set -x for one debug run |
bash -x script.sh |
no edit needed |
bash -x script.sh > trace.log |
bash -x … 2> trace.log |
trace is on stderr, not stdout |
bash -x on an executable script that has its own shebang |
use the shebang: ./script.sh then set -x inside |
bash -x ignores the shebang’s interpreter, may run with wrong shell |
Running bash -n then assuming logic is correct |
bash -n only checks syntax |
logical bugs still pass |
Forgetting to clean up the trace.log afterward |
delete or .gitignore it |
secrets may end up in there |
Next → ShellCheck