@@ -71,7 +71,11 @@ __bp_inside_precmd=0
71
71
__bp_inside_preexec=0
72
72
73
73
# 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 "$_"'
75
79
76
80
# Fails if any of the given variables are readonly
77
81
# Reference https://stackoverflow.com/a/4441178
@@ -157,21 +161,38 @@ __bp_precmd_invoke_cmd() {
157
161
return
158
162
fi
159
163
local __bp_inside_precmd=1
164
+ bash_preexec_invoke_precmd_functions " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
165
+
166
+ __bp_set_ret_value " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
167
+ }
160
168
169
+ # This function invokes every function defined in our function array
170
+ # "precmd_function". This function receives the arguments $1 and $2 for $? and
171
+ # $_, respectively, that will be set for the precmd functions. This function
172
+ # returns the last non-zero exit status of the hook functions. If there is no
173
+ # error, this function returns 0.
174
+ bash_preexec_invoke_precmd_functions () {
175
+ local lastexit=$1 lastarg=$2
161
176
# Invoke every function defined in our function array.
162
177
local precmd_function
178
+ local precmd_function_ret_value
179
+ local precmd_ret_value=0
163
180
for precmd_function in " ${precmd_functions[@]} " ; do
164
181
165
182
# Only execute this function if it actually exists.
166
183
# Test existence of functions with: declare -[Ff]
167
184
if type -t " $precmd_function " 1> /dev/null; then
168
- __bp_set_ret_value " $__bp_last_ret_value " " $__bp_last_argument_prev_command "
185
+ __bp_set_ret_value " $lastexit " " $lastarg "
169
186
# Quote our function invocation to prevent issues with IFS
170
187
" $precmd_function "
188
+ precmd_function_ret_value=$?
189
+ if [[ " $precmd_function_ret_value " != 0 ]]; then
190
+ precmd_ret_value=" $precmd_function_ret_value "
191
+ fi
171
192
fi
172
193
done
173
194
174
- __bp_set_ret_value " $__bp_last_ret_value "
195
+ __bp_set_ret_value " $precmd_ret_value "
175
196
}
176
197
177
198
# Sets a return value in $?. We may want to get access to the $? variable in our
@@ -260,7 +281,27 @@ __bp_preexec_invoke_exec() {
260
281
return
261
282
fi
262
283
263
- # Invoke every function defined in our function array.
284
+ bash_preexec_invoke_preexec_functions " ${__bp_last_ret_value:- } " " $__bp_last_argument_prev_command " " $this_command "
285
+ local preexec_ret_value=$?
286
+
287
+ # Restore the last argument of the last executed command, and set the return
288
+ # value of the DEBUG trap to be the return code of the last preexec function
289
+ # to return an error.
290
+ # If `extdebug` is enabled a non-zero return value from any preexec function
291
+ # will cause the user's command not to execute.
292
+ # Run `shopt -s extdebug` to enable
293
+ __bp_set_ret_value " $preexec_ret_value " " $__bp_last_argument_prev_command "
294
+ }
295
+
296
+ # This function invokes every function defined in our function array
297
+ # "preexec_function". This function receives the arguments $1 and $2 for $?
298
+ # and $_, respectively, that will be set for the preexec functions. The third
299
+ # argument $3 specifies the user command that is going to be executed
300
+ # (corresponding to BASH_COMMAND in the DEBUG trap). This function returns the
301
+ # last non-zero exit status from the preexec functions. If there is no error,
302
+ # this function returns `0`.
303
+ bash_preexec_invoke_preexec_functions () {
304
+ local lastexit=$1 lastarg=$2 this_command=$3
264
305
local preexec_function
265
306
local preexec_function_ret_value
266
307
local preexec_ret_value=0
@@ -269,7 +310,7 @@ __bp_preexec_invoke_exec() {
269
310
# Only execute each function if it actually exists.
270
311
# Test existence of function with: declare -[fF]
271
312
if type -t " $preexec_function " 1> /dev/null; then
272
- __bp_set_ret_value " ${__bp_last_ret_value :- } "
313
+ __bp_set_ret_value " $lastexit " " $lastarg "
273
314
# Quote our function invocation to prevent issues with IFS
274
315
" $preexec_function " " $this_command "
275
316
preexec_function_ret_value=" $? "
@@ -278,14 +319,7 @@ __bp_preexec_invoke_exec() {
278
319
fi
279
320
fi
280
321
done
281
-
282
- # Restore the last argument of the last executed command, and set the return
283
- # value of the DEBUG trap to be the return code of the last preexec function
284
- # to return an error.
285
- # If `extdebug` is enabled a non-zero return value from any preexec function
286
- # will cause the user's command not to execute.
287
- # Run `shopt -s extdebug` to enable
288
- __bp_set_ret_value " $preexec_ret_value " " $__bp_last_argument_prev_command "
322
+ __bp_set_ret_value " $preexec_ret_value "
289
323
}
290
324
291
325
__bp_install () {
@@ -294,7 +328,8 @@ __bp_install() {
294
328
return 1;
295
329
fi
296
330
297
- trap ' __bp_preexec_invoke_exec "$_"' DEBUG
331
+ # shellcheck disable=SC2064
332
+ trap " $bash_preexec_trapdebug_string " DEBUG
298
333
299
334
# Preserve any prior DEBUG trap as a preexec function
300
335
local prior_trap
@@ -327,7 +362,7 @@ __bp_install() {
327
362
# Remove setting our trap install string and sanitize the existing prompt command string
328
363
existing_prompt_command=" ${PROMPT_COMMAND:- } "
329
364
# Edge case of appending to PROMPT_COMMAND
330
- existing_prompt_command=" ${existing_prompt_command// $__bp_install_string /: } " # no-op
365
+ existing_prompt_command=" ${existing_prompt_command// $bash_preexec_install_string /: } " # no-op
331
366
existing_prompt_command=" ${existing_prompt_command// $' \n ' : $' \n ' / $' \n ' } " # remove known-token only
332
367
existing_prompt_command=" ${existing_prompt_command// $' \n ' : ;/ $' \n ' } " # remove known-token only
333
368
__bp_sanitize_string existing_prompt_command " $existing_prompt_command "
@@ -346,10 +381,13 @@ __bp_install() {
346
381
PROMPT_COMMAND+=$' \n __bp_interactive_mode'
347
382
fi
348
383
349
- # Add two functions to our arrays for convenience
350
- # of definition.
351
- precmd_functions+=(precmd)
352
- 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
353
391
354
392
# Invoke our two functions manually that were added to $PROMPT_COMMAND
355
393
__bp_precmd_invoke_cmd
@@ -371,8 +409,46 @@ __bp_install_after_session_init() {
371
409
PROMPT_COMMAND=${sanitized_prompt_command} $' \n '
372
410
fi ;
373
411
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
374
- 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
375
448
}
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
376
452
377
453
# Run our install so long as we're not delaying it.
378
454
if [[ -z " ${__bp_delay_install:- } " ]]; then
0 commit comments