Skip to content

Commit 431c050

Browse files
committed
Check PROMPT_COMMAND in precmd and adjust it if necessary
1 parent e96a78c commit 431c050

File tree

2 files changed

+96
-15
lines changed

2 files changed

+96
-15
lines changed

bash-preexec.sh

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,39 @@ __bp_sanitize_string() {
163163
fi
164164
}
165165

166+
167+
# Bash >= 5.1 supports the array version of PROMPT_COMMAND.
168+
__bp_use_array_prompt_command() {
169+
(( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) ))
170+
}
171+
172+
173+
# Remove $1 and sanitize each elements of PROMPT_COMMAND. We want to keep
174+
# PROMPT_COMMAND scalar in bash < 5.1 because some configuration tests the
175+
# support for the array PROMPT_COMMAND by checking the array attribute of
176+
# PROMPT_COMMAND.
177+
__bp_remove_command_from_prompt_command() {
178+
local removed_command="${1-}"
179+
if __bp_use_array_prompt_command; then
180+
local i sanitized_prompt_command
181+
for i in "${!PROMPT_COMMAND[@]}"; do
182+
sanitized_prompt_command="${PROMPT_COMMAND[i]:-}"
183+
sanitized_prompt_command="${sanitized_prompt_command//"$removed_command"/:}"
184+
__bp_sanitize_string sanitized_prompt_command "$sanitized_prompt_command"
185+
if [[ -n "$sanitized_prompt_command" ]]; then
186+
PROMPT_COMMAND[i]="$sanitized_prompt_command"
187+
else
188+
unset -v 'PROMPT_COMMAND[i]'
189+
fi
190+
done
191+
else
192+
local sanitized_prompt_command="${PROMPT_COMMAND:-}"
193+
sanitized_prompt_command="${sanitized_prompt_command//"$removed_command"/:}" # no-op
194+
__bp_sanitize_string PROMPT_COMMAND "$sanitized_prompt_command"
195+
fi
196+
}
197+
198+
166199
# This function is installed as part of the PROMPT_COMMAND;
167200
# It sets a variable to indicate that the prompt was just displayed,
168201
# to allow the DEBUG trap to know that the next command is likely interactive.
@@ -187,6 +220,11 @@ __bp_precmd_invoke_cmd() {
187220
if (( __bp_inside_precmd > 0 )); then
188221
return
189222
fi
223+
224+
# Check and adjust PROMPT_COMMAND to make sure that PROMPT_COMMAND has the
225+
# form "__bp_precmd_invoke_cmd; ...; __bp_interactive_mode"
226+
__bp_install_prompt_command
227+
190228
local __bp_inside_precmd=1
191229

192230
# Invoke every function defined in our function array.
@@ -354,23 +392,10 @@ __bp_install() {
354392
shopt -s extdebug > /dev/null 2>&1
355393
fi;
356394

357-
local existing_prompt_command
358395
# Remove setting our trap install string and sanitize the existing prompt command string
359-
existing_prompt_command="${PROMPT_COMMAND:-}"
360-
# Edge case of appending to PROMPT_COMMAND
361-
existing_prompt_command="${existing_prompt_command//$__bp_install_string/:}" # no-op
362-
__bp_sanitize_string existing_prompt_command "$existing_prompt_command"
396+
__bp_remove_command_from_prompt_command "$__bp_install_string"
363397

364-
# Install our hooks in PROMPT_COMMAND to allow our trap to know when we've
365-
# actually entered something.
366-
PROMPT_COMMAND='__bp_precmd_invoke_cmd'
367-
PROMPT_COMMAND+=${existing_prompt_command:+$'\n'$existing_prompt_command}
368-
if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) )); then
369-
PROMPT_COMMAND+=('__bp_interactive_mode')
370-
else
371-
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
372-
PROMPT_COMMAND+=$'\n__bp_interactive_mode'
373-
fi
398+
__bp_install_prompt_command
374399

375400
# Add two functions to our arrays for convenience
376401
# of definition.
@@ -382,6 +407,38 @@ __bp_install() {
382407
__bp_interactive_mode
383408
}
384409

410+
411+
# Encloses PROMPT_COMMAND hooks within __bp_precmd_invoke_cmd and
412+
# __bp_interactive_mode.
413+
__bp_install_prompt_command() {
414+
local prompt_command="${PROMPT_COMMAND:-}"
415+
if __bp_use_array_prompt_command; then
416+
local IFS=$'\n'
417+
prompt_command="${PROMPT_COMMAND[*]:-}"
418+
IFS=$' \t\n'
419+
fi
420+
421+
# Exit if we already have a properly set-up hooks in PROMPT_COMMAND
422+
if [[ "$prompt_command" == __bp_precmd_invoke_cmd$'\n'*$'\n'__bp_interactive_mode ]]; then
423+
return 0
424+
fi
425+
426+
__bp_remove_command_from_prompt_command __bp_precmd_invoke_cmd
427+
__bp_remove_command_from_prompt_command __bp_interactive_mode
428+
429+
# Install our hooks in PROMPT_COMMAND to allow our trap to know when we've
430+
# actually entered something.
431+
# shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
432+
PROMPT_COMMAND='__bp_precmd_invoke_cmd'${PROMPT_COMMAND:+$'\n'$PROMPT_COMMAND}
433+
if __bp_use_array_prompt_command; then
434+
PROMPT_COMMAND+=('__bp_interactive_mode')
435+
else
436+
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
437+
PROMPT_COMMAND+=$'\n__bp_interactive_mode'
438+
fi
439+
}
440+
441+
385442
# Sets an installation string as part of our PROMPT_COMMAND to install
386443
# after our session has started. This allows bash-preexec to be included
387444
# at any point in our bash profile.

test/bash-preexec.bats

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ set_exit_code_and_run_precmd() {
103103
(( trap_count_snapshot < trap_invoked_count ))
104104
}
105105

106+
@test "__bp_install_prompt_command should adjust modified PROMPT_COMMAND" {
107+
unset -v PROMPT_COMMAND
108+
PROMPT_COMMAND="echo PREHOOK"
109+
110+
# First install
111+
__bp_install_prompt_command
112+
expected_result=$'__bp_precmd_invoke_cmd\necho PREHOOK\n__bp_interactive_mode'
113+
[ "$(join_PROMPT_COMMAND)" == "$expected_result" ]
114+
115+
# User modification
116+
if (( ${#PROMPT_COMMAND[@]} == 2 )); then
117+
PROMPT_COMMAND+=('echo POSTHOOK')
118+
else
119+
PROMPT_COMMAND+=$'\necho POSTHOOK'
120+
fi
121+
expected_result=$'__bp_precmd_invoke_cmd\necho PREHOOK\n__bp_interactive_mode\necho POSTHOOK'
122+
[ "$(join_PROMPT_COMMAND)" == "$expected_result" ]
123+
124+
# Re-adjust
125+
__bp_install_prompt_command
126+
expected_result=$'__bp_precmd_invoke_cmd\necho PREHOOK\necho POSTHOOK\n__bp_interactive_mode'
127+
[ "$(join_PROMPT_COMMAND)" == "$expected_result" ]
128+
}
129+
106130
@test "__bp_sanitize_string should remove semicolons and trim space" {
107131

108132
__bp_sanitize_string output " true1; "$'\n'

0 commit comments

Comments
 (0)