Tracing with set -x, PS4, and Selective Debug Output
Bash tracing can show exactly what the shell executes after expansion, but indiscriminate xtrace is noisy and can expose secrets. Effective debugging narrows the trace and gives every line useful context.
Learning objectives
By the end of this lesson
- Explain what xtrace reveals.
- Customize PS4 context.
- Route traces separately.
- Protect secret-bearing operations.
- Choose selective debug logging when appropriate.
1. xtrace shows commands after expansion
set -x enables Bash execution tracing. Bash prints each command after parameter, command, and arithmetic expansion, immediately before execution.
flowchart LR S["source command"] --> E["shell expansions"] E --> X["xtrace line"] E --> C["command execution"]
Expanded values can include credentials. Never treat xtrace as safe around secrets.
2. Enable and disable tracing selectively
set -x
build_project
run_tests
set +x
publish_secret_bearing_stepA short trace window is easier to audit and far easier to read than tracing an entire pipeline.
3. PS4 adds useful context
PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}: '
set -x
do_work() {
printf 'working\n'
}
do_work
set +xFile, line, and function names turn trace output into a navigable execution record.
4. Route traces to a dedicated descriptor
exec 19>trace.log
export BASH_XTRACEFD=19
PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x
run_build
set +x
exec 19>&-BASH_XTRACEFD can separate trace output from stdout and stderr on supported Bash versions.
5. Disable tracing before credentials are expanded
set -x
prepare_request
set +x
curl \
--header "Authorization: Bearer $API_TOKEN" \
--silent \
"$url"
set -x
verify_response
set +xCI masking may not recognize transformed, encoded, or partial secret values.
6. Use a subshell to contain debug options
(
PS4='+ debug:${LINENO}: '
set -x
reproduce_failure
)
printf 'parent tracing unchanged\n'Subshell containment is useful when you want debug state to disappear automatically.
7. Custom debug logging is often safer
DEBUG=${DEBUG:-false}
debug() {
[[ $DEBUG == true ]] || return 0
printf 'DEBUG phase=%q service=%q\n' "$phase" "$service" >&2
}A custom logger reveals only the fields you deliberately choose.
8. xtrace and verbose input are different
set -x traces execution after expansion; set -v prints shell input as read. Use the mode that answers the actual debugging question.
9. Rich PS4 expressions have a cost
PS4='+ $(date -u "+%H:%M:%S") ${LINENO}: '
set -x
slow_operation
set +xCommand substitutions in PS4 run for every trace line. Use expensive prefixes only while diagnosing a problem.
10. Hands-on lab: targeted trace file
mkdir -p "$HOME/devops-academy/bash/chapter17/lesson01"
cd "$HOME/devops-academy/bash/chapter17/lesson01"
cat > trace-demo.sh <<'EOF'
#!/usr/bin/env bash
set -u
exec 19>trace.log
export BASH_XTRACEFD=19
PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}: '
calculate() {
local a=$1 b=$2
printf 'total=%d\n' "$((a + b))"
}
set -x
calculate 3 4
set +x
secret='never-trace-this'
printf 'secret_present=%s\n' "$([[ -n $secret ]] && printf yes)"
exec 19>&-
EOF
chmod u+x trace-demo.sh
bash trace-demo.sh
cat trace.logVerification checklist
11. Knowledge check
Question 1. What does xtrace print?
Question 2. What does PS4 control?
Question 3. Why use BASH_XTRACEFD?
Question 4. When is custom debug logging preferable?
12. Summary
Use xtrace as a scalpel: narrow its scope, enrich it with PS4, separate it from ordinary output, and turn it off before secret-bearing operations.
13. Further reading
- GNU Bash Reference Manual — The Set Builtin.
- GNU Bash Reference Manual —
PS4andBASH_XTRACEFD. - ShellCheck documentation.
- OWASP secret-safe logging guidance.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.