@@ -71,7 +71,11 @@ __bp_inside_precmd=0
7171__bp_inside_preexec=0
7272
7373# Initial PROMPT_COMMAND string that is removed from PROMPT_COMMAND post __bp_install
74- __bp_install_string=$' __bp_trap_string="$(trap -p DEBUG)"\n trap - DEBUG\n __bp_install'
74+ bash_preexec_install_string=$' __bp_trap_string="$(trap -p DEBUG)"\n trap - DEBUG\n __bp_install'
75+
76+ # The command string that is registered to the DEBUG trap.
77+ # shellcheck disable=SC2016
78+ bash_preexec_trapdebug_string=' __bp_preexec_invoke_exec "$_"'
7579
7680# Fails if any of the given variables are readonly
7781# Reference https://stackoverflow.com/a/4441178
@@ -157,7 +161,7 @@ __bp_precmd_invoke_cmd() {
157161 return
158162 fi
159163 local __bp_inside_precmd=1
160- __bp_invoke_precmd_functions " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
164+ bash_preexec_invoke_precmd_functions " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
161165
162166 __bp_set_ret_value " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
163167}
@@ -167,7 +171,7 @@ __bp_precmd_invoke_cmd() {
167171# $_, respectively, which will be set for each precmd function. This function
168172# returns the last non-zero exit status of the hook functions. If there is no
169173# error, this function returns 0.
170- __bp_invoke_precmd_functions () {
174+ bash_preexec_invoke_precmd_functions () {
171175 local lastexit=$1 lastarg=$2
172176 # Invoke every function defined in our function array.
173177 local precmd_function
@@ -277,7 +281,7 @@ __bp_preexec_invoke_exec() {
277281 return
278282 fi
279283
280- __bp_invoke_preexec_functions " ${__bp_last_ret_value:- } " " $__bp_last_argument_prev_command " " $this_command "
284+ bash_preexec_invoke_preexec_functions " ${__bp_last_ret_value:- } " " $__bp_last_argument_prev_command " " $this_command "
281285 local preexec_ret_value=$?
282286
283287 # Restore the last argument of the last executed command, and set the return
@@ -296,7 +300,7 @@ __bp_preexec_invoke_exec() {
296300# (corresponding to BASH_COMMAND in the DEBUG trap). This function returns the
297301# last non-zero exit status from the preexec functions. If there is no error,
298302# this function returns `0`.
299- __bp_invoke_preexec_functions () {
303+ bash_preexec_invoke_preexec_functions () {
300304 local lastexit=$1 lastarg=$2 this_command=$3
301305 local preexec_function
302306 local preexec_function_ret_value
@@ -324,7 +328,8 @@ __bp_install() {
324328 return 1
325329 fi
326330
327- trap ' __bp_preexec_invoke_exec "$_"' DEBUG
331+ # shellcheck disable=SC2064
332+ trap " $bash_preexec_trapdebug_string " DEBUG
328333
329334 # Preserve any prior DEBUG trap as a preexec function
330335 local prior_trap
@@ -357,7 +362,7 @@ __bp_install() {
357362 # Remove setting our trap install string and sanitize the existing prompt command string
358363 existing_prompt_command=" ${PROMPT_COMMAND:- } "
359364 # Edge case of appending to PROMPT_COMMAND
360- existing_prompt_command=" ${existing_prompt_command// $__bp_install_string /: } " # no-op
365+ existing_prompt_command=" ${existing_prompt_command// $bash_preexec_install_string /: } " # no-op
361366 existing_prompt_command=" ${existing_prompt_command// $' \n ' : $' \n ' / $' \n ' } " # remove known-token only
362367 existing_prompt_command=" ${existing_prompt_command// $' \n ' : ;/ $' \n ' } " # remove known-token only
363368 __bp_sanitize_string existing_prompt_command " $existing_prompt_command "
@@ -376,10 +381,13 @@ __bp_install() {
376381 PROMPT_COMMAND+=$' \n __bp_interactive_mode'
377382 fi
378383
379- # Add two functions to our arrays for convenience
380- # of definition.
381- precmd_functions+=(precmd)
382- preexec_functions+=(preexec)
384+ # Add two functions to our arrays for convenience of definition only when
385+ # the functions have not yet added.
386+ if [[ ! ${__bp_installed_convenience_functions-} ]]; then
387+ __bp_installed_convenience_functions=1
388+ precmd_functions+=(precmd)
389+ preexec_functions+=(preexec)
390+ fi
383391
384392 # Invoke our two functions manually that were added to $PROMPT_COMMAND
385393 __bp_precmd_invoke_cmd
@@ -401,8 +409,46 @@ __bp_install_after_session_init() {
401409 PROMPT_COMMAND=${sanitized_prompt_command} $' \n '
402410 fi
403411 # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
404- PROMPT_COMMAND+=${__bp_install_string}
412+ PROMPT_COMMAND+=${bash_preexec_install_string}
413+ }
414+
415+ # Remove hooks installed in the DEBUG trap and PROMPT_COMMAND.
416+ bash_preexec_uninstall () {
417+ # Remove __bp_install hook from PROMPT_COMMAND
418+ # shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
419+ if [[ ${PROMPT_COMMAND-} == * " $bash_preexec_install_string " * ]]; then
420+ PROMPT_COMMAND=" ${PROMPT_COMMAND// ${bash_preexec_install_string} [;$'\n']} " # Edge case of appending to PROMPT_COMMAND
421+ PROMPT_COMMAND=" ${PROMPT_COMMAND// $bash_preexec_install_string } "
422+ fi
423+
424+ # Remove precmd hook from PROMPT_COMMAND
425+ local i prompt_command
426+ for i in " ${! PROMPT_COMMAND[@]} " ; do
427+ prompt_command=${PROMPT_COMMAND[i]}
428+ case $prompt_command in
429+ __bp_precmd_invoke_cmd | __bp_interactive_mode)
430+ prompt_command= ;;
431+ * )
432+ prompt_command=${prompt_command/# $' __bp_precmd_invoke_cmd\n ' / $' \n ' }
433+ prompt_command=${prompt_command% $' \n __bp_interactive_mode' }
434+ prompt_command=${prompt_command# $' \n ' }
435+ esac
436+ PROMPT_COMMAND[i]=$prompt_command
437+ done
438+
439+ # Remove preexec hook in the DEBUG trap
440+ local q=" '" Q=" '\''"
441+ if [[ $( trap -p DEBUG) == " trap -- '${bash_preexec_trapdebug_string// $q / $Q } ' DEBUG" ]]; then
442+ if [[ ${__bp_trap_string-} ]]; then
443+ eval -- " $__bp_trap_string "
444+ else
445+ trap - DEBUG
446+ fi
447+ fi
405448}
449+ # Note: We need to add "trace" attribute to the function so that "trap - DEBUG"
450+ # inside the function takes an effect.
451+ declare -ft bash_preexec_uninstall
406452
407453# Run our install so long as we're not delaying it.
408454if [[ -z " ${__bp_delay_install:- } " ]]; then
0 commit comments