June 8, 2014 · Linux

DEBUG trap and PROMPT_COMMAND in Bash

Update 03/08/2016: A patch by Dan Stromberg adds a PS0 variable to Bash that greatly simplifies what’s described in this article. This patch will likely be merged into Bash 4.4. Please refer to his post for details.

The DEBUG trap

The DEBUG trap is an extremely handy feature of Bash. The idea is pretty straightforward: if you run

trap "echo Hello" DEBUG

then Bash will run echo Hello before it executes each subsequent command. For example:

~/Scratch $ ls
Hello
file1 file2
~/Scratch $ echo Bye
Hello
Bye

A caveat, however, is that the DEBUG trap is triggered once per simple command; if you have command lists or control structures, the trap will be triggered multiple times. For example, using the setup above:

~/Scratch $ echo 1 && echo 2; echo 3
Hello
1
Hello
2
Hello
3
~/Scratch $ if [ -e /etc/passwd ]; then echo "/etc/passwd exists"; fi
Hello
Hello
/etc/passwd exists

What if we only want to run a command once per composite command, like the preexec hook in zsh?

Enter PROMPT_COMMAND.

PROMPT_COMMAND

The idea behind PROMPT_COMMAND is also very simple: if you run

PROMPT_COMMAND="echo Bye"

then Bash will execute echo Bye before it prints each subsequent prompt (i.e., after it has finished executing the previous command line). For example, using the setup above:

~/Scratch $ echo 1; echo 2
Hello
1
Hello
2
Hello
Bye

Note that the DEBUG trap is triggered again for PROMPT_COMMAND, in addition to the user-supplied commands.

Combining the DEBUG trap and PROMPT_COMMAND

By combining the DEBUG trap and PROMPT_COMMAND, we can now hack Bash to run some code right before and right after executing a full command. For example, try adding this to your ~/.bashrc:

# This will run before any command is executed.
function PreCommand() {
  if [ -z "$AT_PROMPT" ]; then
    return
  fi
  unset AT_PROMPT

  # Do stuff.
  echo "Running PreCommand"
}
trap "PreCommand" DEBUG

# This will run after the execution of the previous full command line.  We don't
# want it PostCommand to execute when first starting a bash session (i.e., at
# the first prompt).
FIRST_PROMPT=1
function PostCommand() {
  AT_PROMPT=1

  if [ -n "$FIRST_PROMPT" ]; then
    unset FIRST_PROMPT
    return
  fi

  # Do stuff.
  echo "Running PostCommand"
}
PROMPT_COMMAND="PostCommand"

The result:

~/Scratch $ echo 1; echo 2 && echo 3
Running PreCommand
1
2
3
Running PostCommand

This gives rise to some neat applications, such as a command timer script I wrote that prints out the execution time of each command:

bash_command_timer_screenshot-1

Please feel free to check it out on GitHub :)

Happy Bash hacking!

  • LinkedIn
  • Tumblr
  • Reddit
  • Pinterest
  • Pocket