Linux Basic — bash -x Flag

3 minute read

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:

  • -e exit on error
  • -u exit on unset var
  • -x trace
  • -o pipefail catch 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

Updated: