Skip to content
shellhacks.dev
Debugging

Trace with line numbers

Add the file name, line number and function to every set -x line by setting PS4 first. The default plus sign prefix gives no hint where a command came from.

2 min read
Summarize in AI

set -x prints each command, but in a long script with includes you cannot tell which file or line a trace line belongs to.

Shell
cat > demo.sh <<'EOF'
#!/bin/bash
PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-main}: '
set -x
greet() {
  local name=$1
  echo "hello $name"
}
greet world
greet bash
EOF
bash demo.sh
Text
+ demo.sh:8:main: greet world
+ demo.sh:5:greet: local name=world
+ demo.sh:6:greet: echo 'hello world'
hello world
+ demo.sh:9:main: greet bash
+ demo.sh:5:greet: local name=bash
+ demo.sh:6:greet: echo 'hello bash'
hello bash

Bash expands PS4 before each traced command, and the prefix is printed first, repeated once per level of nesting. The manual lists the variables a prompt can use under shell variables. BASH_SOURCE is the file, LINENO the line and FUNCNAME the function, so each trace line now says where it came from. The local assignment and the echo show the function name in the third field.

Single quotes matter: with double quotes the variables expand once when PS4 is set, and every line prints the same location. Set PS4 inside the script, before set -x. Bash ignores a PS4 inherited from the environment when it runs as root, so PS4=... bash -x script printed no location in the run here, although it works for a normal user. zsh uses PS4='%N:%i> ', and has no FUNCNAME.

Share

More snippets

Debugging

Stop on the first error and show where

Report where a script failed with trap '...' ERR and set -E. LINENO gives the line, BASH_COMMAND the command and $? the status, even inside functions.

1 min read
Debugging

Print each line as it is read

Print every script line as the shell reads it, before expansion, with set -v. Pair it with set -x to see the source line and the expanded command.

1 min read

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.