Read-only pull mirror of github.com/rcaloras/bash-preexec. Aurora's build fetches bash-preexec.sh from here.
Find a file
George Nachman d0498b0bca Preserve $? and $_ on __bp_precmd_invoke_cmd early returns
__bp_precmd_invoke_cmd saves the previous command's exit status into
__bp_last_ret_value at the top and restores it via __bp_set_ret_value
on the normal exit path. Two early-return paths bypass that restore:

1. The `(( __bp_inside_precmd > 0 )); then return` reentrancy guard
   leaves $? equal to the exit status of the `(( ... ))` test, which
   is always 0 when the condition is true.

2. The `return 0` inside the nested-call-frame branch (the "another
   framework saved our PROMPT_COMMAND" case) explicitly clobbers $?
   to 0.

Both paths run before reaching the trailing __bp_set_ret_value, so
the next command in PROMPT_COMMAND (and any direct caller of
__bp_precmd_invoke_cmd) sees $? = 0 instead of the user's actual
last exit status. Add an explicit __bp_set_ret_value call (and
replace `return 0` with a bare `return`) on both early-return paths
so $? and $_ flow through cleanly.

Includes bats coverage for both paths. The path-2 test clears
BATS_VERSION inside the nested call because the path's own guard
short-circuits when BATS_VERSION is set (so bats's own scaffolding
doesn't trip it).
2026-06-19 19:02:59 +09:00
.github/workflows Test against Bash 3.2, 4.4, 5.0, 5.3 in CI (#177) 2026-03-06 14:45:53 -05:00
test Preserve $? and $_ on __bp_precmd_invoke_cmd early returns 2026-06-19 19:02:59 +09:00
bash-preexec.sh Preserve $? and $_ on __bp_precmd_invoke_cmd early returns 2026-06-19 19:02:59 +09:00
LICENSE.md Update LICENSE.md 2017-09-24 18:38:51 -04:00
README.md Fix Bashhub logo link in README 2026-04-15 06:50:39 -04:00

Build Status GitHub version

Bash-Preexec

preexec and precmd hook functions for Bash 3.1+ in the style of Zsh. They aim to emulate the behavior as described for Zsh.

Bashhub Logo

This project is currently being used in production by Bashhub, iTerm2, and Ghostty. Hype!

Quick Start

# Pull down our file from GitHub and write it to your home directory as a hidden file.
curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh
# Source our file to bring it into our environment
source ~/.bash-preexec.sh
# Define a couple functions.
preexec() { echo "just typed $1"; }
precmd() { echo "printing the prompt"; }

Install

You'll want to pull down the file and add it to your bash profile/configuration (i.e ~/.bashrc, ~/.profile, ~/.bash_profile, etc). It must be the last thing imported in your bash profile.

# Pull down our file from GitHub and write it to your home directory as a hidden file.
curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh
# Source our file at the end of our bash profile (e.g. ~/.bashrc, ~/.profile, or ~/.bash_profile)
echo '[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh' >> ~/.bashrc

Usage

Two functions preexec and precmd can now be defined and they'll be automatically invoked by bash-preexec if they exist.

  • preexec Executed just after a command has been read and is about to be executed. The string that the user typed is passed as the first argument.
  • precmd Executed just before each prompt. Equivalent to PROMPT_COMMAND, but more flexible and resilient.
source ~/.bash-preexec.sh
preexec() { echo "just typed $1"; }
precmd() { echo "printing the prompt"; }

Should output something like:

elementz@Kashmir:~/git/bash-preexec (master)$ ls
just typed ls
bash-preexec.sh  README.md  test
printing the prompt

Function Arrays

You can also define functions to be invoked by appending them to two different arrays. This is great if you want to have many functions invoked for either hook. Both preexec and precmd functions are added to these by default and don't need to be added manually.

  • $preexec_functions Array of functions invoked by preexec.
  • $precmd_functions Array of functions invoked by precmd.

preexec

# Define some function to use preexec
preexec_hello_world() { echo "You just entered $1"; }
# Add it to the array of functions to be invoked each time.
preexec_functions+=(preexec_hello_world)

precmd

precmd_hello_world() { echo "This is invoked before the prompt is displayed"; }
precmd_functions+=(precmd_hello_world)

You can also define multiple functions to be invoked like so.

precmd_hello_one() { echo "This is invoked on precmd first"; }
precmd_hello_two() { echo "This is invoked on precmd second"; }
precmd_functions+=(precmd_hello_one)
precmd_functions+=(precmd_hello_two)

You can check the functions set for each by echoing its contents.

echo ${preexec_functions[@]}
echo ${precmd_functions[@]}

Subshells

bash-preexec does not support invoking preexec() for subshells by default. It must be enabled by setting __bp_enable_subshells.

# Enable experimental subshell support
export __bp_enable_subshells="true"

This is disabled by default due to buggy situations related to to functrace and Bash's DEBUG trap. See Issue #25

Library authors

If you want to detect bash-preexec in your library (for example, to add hooks to preexec_functions when available), use the Bash variable bash_preexec_imported:

if [[ -n "${bash_preexec_imported:-}" ]]; then
    echo "Bash-preexec is loaded."
fi

Tests

You can run tests using Bats.

bats test

Should output something like:

elementz@Kashmir:~/git/bash-preexec(master)$ bats test
 ✓ No functions defined for preexec should simply return
 ✓ precmd should execute a function once
 ✓ preexec should execute a function with the last command in our history
 ✓ preexec should execute multiple functions in the order added to their arrays
 ✓ preecmd should execute multiple functions in the order added to their arrays