Debugging a Bash script is safest when the first checks happen before the script changes data or calls remote systems. Syntax checking catches parse errors, normal execution confirms the expected path, and trace output shows the command sequence when the result is still wrong.
Bash can parse a script without running it with bash -n and can print each command before execution with bash -x. A custom PS4 prefix adds file and line details to trace lines, which helps connect a failing command to the script source.
The example uses a temporary PS4 line near the top of the script and writes the trace stream to trace.log so normal output remains separate while debugging. Keep trace files local until they have been reviewed, because trace lines can contain arguments, paths, tokens, and environment values.
Related: How to create and run a Bash script
Related: How to create a function in Bash
#!/usr/bin/env bash set -euo pipefail PS4='+ ${BASH_SOURCE##*/}:${LINENO}: ' prepare_item() { local name=$1 printf 'item=%s\n' "$name" } input=${1:-sample} prepare_item "$input"
Single quotes around PS4 keep ${BASH_SOURCE##*/} and ${LINENO} from expanding when the variable is assigned, so each trace line can show the file and line being executed.
$ bash -n debug-demo.sh
No output from bash -n means Bash parsed the script without a syntax error.
$ bash debug-demo.sh report item=report
$ bash -x debug-demo.sh report 2>trace.log item=report
Trace output goes to standard error, so 2>trace.log keeps it separate from normal standard output.
$ cat trace.log
+ set -euo pipefail
+ PS4='+ ${BASH_SOURCE##*/}:${LINENO}: '
+ debug-demo.sh:11: input=report
+ debug-demo.sh:12: prepare_item report
+ debug-demo.sh:7: local name=report
+ debug-demo.sh:8: printf 'item=%s\n' report
$ rm trace.log
Do not commit trace logs or paste them into tickets until sensitive values have been removed. Remove the temporary PS4 assignment too if the script should not keep debug trace metadata.