Merge pull request #180 from Rogach/fix/login-shell-first-preexec

This commit is contained in:
Koichi Murase 2026-04-08 15:56:54 +09:00 committed by GitHub
commit a220343bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 23 deletions

View file

@ -137,19 +137,22 @@ __bp_sanitize_string() {
# We specify newline character through the variable `nl' because $'\n'
# inside "${var//...}" is treated literally as "\$'\\n'" when `extquote' is
# unset (shopt -u extquote). (Note: Bash 5.2's extquote seems to be buggy.)
local tmp sp=$' \t' nl=$'\n'
local tmp nl=$'\n'
while
# Quoting parameter expansions $nl in PAT of ${var//PAT/REP} is
# Note: Quoting parameter expansions $nl in PAT of ${var//PAT/REP} is
# required by shellcheck. On the other hand, we should not quote the
# parameter expansions $nl in REP because the quotes will remain in the
# replaced result with `shopt -s compat42'.
tmp="${sanitized//[";$nl"]*(["$sp"]):*(["$sp"])[";$nl"]/$nl}"
# Note: We use ?(+([[:blank:]])) instead of *([[:blank:]]) to work
# around a bug of Bash 3.2 that *(...) is not properly processed as
# extglob at the beginning of the pattern in ${var//pat/rep}.
tmp="${sanitized//?(+([[:blank:]]))[";$nl"]*([[:blank:]]):*([[:blank:]])[";$nl"]*([[:blank:]])/$nl}"
[[ "$tmp" != "$sanitized" ]]
do
sanitized="$tmp"
done
sanitized="${sanitized#:*(["$sp"])[";$nl"]}"
sanitized="${sanitized%[";$nl"]*(["$sp"]):}"
sanitized="${sanitized#:*([[:blank:]])[";$nl"]}"
sanitized="${sanitized%[";$nl"]*([[:blank:]]):}"
__bp_trim_whitespace sanitized "$sanitized"
sanitized=${sanitized%;}
sanitized=${sanitized#;}
@ -541,14 +544,18 @@ __bp_install_after_session_init() {
__bp_require_not_readonly PS0 || return
fi
local sanitized_prompt_command
__bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}"
if [[ -n "$sanitized_prompt_command" ]]; then
# shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
if __bp_use_array_prompt_command; then
PROMPT_COMMAND+=("${__bp_install_string}")
else
local sanitized_prompt_command
__bp_sanitize_string sanitized_prompt_command "${PROMPT_COMMAND:-}"
if [[ -n "$sanitized_prompt_command" ]]; then
# shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
fi
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
PROMPT_COMMAND+=${__bp_install_string}
fi
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
PROMPT_COMMAND+=${__bp_install_string}
}
# Run our install so long as we're not delaying it.

View file

@ -76,13 +76,28 @@ set_exit_code_and_run_precmd() {
# Assert that before running, the command contains the install string, and
# afterwards it does not
[[ "$PROMPT_COMMAND" == *"$__bp_install_string"* ]] || return 1
[[ "$(join_PROMPT_COMMAND)" == *"$__bp_install_string"* ]] || return 1
eval_PROMPT_COMMAND
[[ "$PROMPT_COMMAND" != *"$__bp_install_string"* ]] || return 1
[[ "$(join_PROMPT_COMMAND)" != *"$__bp_install_string"* ]] || return 1
}
@test "__bp_install should remove trap logic and itself from modified PROMPT_COMMAND" {
PROMPT_COMMAND=()
__bp_install_after_session_init
PROMPT_COMMAND="$PROMPT_COMMAND; true"
# Assert that before running, the command contains the install string, and
# afterwards it does not
[[ "$(join_PROMPT_COMMAND)" == *"$__bp_install_string"* ]] || return 1
eval_PROMPT_COMMAND
[[ "$(join_PROMPT_COMMAND)" != *"$__bp_install_string"* ]] || return 1
}
@test "__bp_install should preserve an existing DEBUG trap" {
trap_invoked_count=0
foo() { (( trap_invoked_count += 1 )); }
@ -236,7 +251,7 @@ set_exit_code_and_run_precmd() {
[ "$output" == "echo USER9" ]
__bp_sanitize_string output $'echo USER10 ; :\n: ; echo USER11'
expected_result=$'echo USER10 \n echo USER11'
expected_result=$'echo USER10\necho USER11'
[ "$output" == "$expected_result" ]
}
@ -250,7 +265,9 @@ set_exit_code_and_run_precmd() {
@test "Appending or prepending to PROMPT_COMMAND should work after bp_install_after_session_init" {
__bp_install_after_session_init
nl=$'\n'
PROMPT_COMMAND="$PROMPT_COMMAND; true"
# On Bash 5.1+ we append to the array properly,
# so first element of PROMPT_COMMAND may still be empty.
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }true"
PROMPT_COMMAND="$PROMPT_COMMAND $nl true"
PROMPT_COMMAND="$PROMPT_COMMAND; true"
PROMPT_COMMAND="true; $PROMPT_COMMAND"
@ -260,6 +277,24 @@ set_exit_code_and_run_precmd() {
eval_PROMPT_COMMAND
}
@test "Appending or prepending to PROMPT_COMMAND array should work after bp_install_after_session_init" {
if __bp_use_array_prompt_command; then
PROMPT_COMMAND=()
__bp_install_after_session_init
nl=$'\n'
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "true")
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "$nl true")
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "true")
PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}")
PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}")
PROMPT_COMMAND=("true" "${PROMPT_COMMAND[@]}")
PROMPT_COMMAND=("true $nl" "${PROMPT_COMMAND[@]}")
eval_PROMPT_COMMAND
else
skip
fi
}
# Case where a user is appending or prepending to PROMPT_COMMAND.
# This can happen after 'source bash-preexec.sh' e.g.
# source bash-preexec.sh; PROMPT_COMMAND="$PROMPT_COMMAND; other_prompt_command_hook"
@ -267,23 +302,27 @@ set_exit_code_and_run_precmd() {
PROMPT_COMMAND="echo before"
PROMPT_COMMAND="$PROMPT_COMMAND; echo before2"
__bp_install_after_session_init
PROMPT_COMMAND="$PROMPT_COMMAND"$'\n echo after'
PROMPT_COMMAND="$PROMPT_COMMAND"$'\necho after'
PROMPT_COMMAND="echo after2; $PROMPT_COMMAND;"
eval_PROMPT_COMMAND
expected_result=$'__bp_precmd_invoke_cmd "$_"\necho after2; echo before; echo before2\n echo after\n__bp_interactive_mode'
expected_result=$'__bp_precmd_invoke_cmd "$_"\necho after2; echo before; echo before2\necho after\n__bp_interactive_mode'
[ "$(join_PROMPT_COMMAND)" == "$expected_result" ]
}
@test "Adding to PROMPT_COMMAND after with semicolon" {
PROMPT_COMMAND="echo before"
__bp_install_after_session_init
PROMPT_COMMAND="$PROMPT_COMMAND; echo after"
if __bp_use_array_prompt_command; then
PROMPT_COMMAND[${#PROMPT_COMMAND[@]}-1]+='; echo after'
else
PROMPT_COMMAND+='; echo after'
fi
eval_PROMPT_COMMAND
expected_result=$'__bp_precmd_invoke_cmd "$_"\necho before\n echo after\n__bp_interactive_mode'
expected_result=$'__bp_precmd_invoke_cmd "$_"\necho before\necho after\n__bp_interactive_mode'
[ "$(join_PROMPT_COMMAND)" == "$expected_result" ]
}
@ -296,12 +335,38 @@ set_exit_code_and_run_precmd() {
precmd() { echo "inside precmd"; }
run eval_PROMPT_COMMAND
[ "${#lines[@]}" == '5' ]
[ "${lines[0]}" == "after2" ]
[ "${lines[1]}" == "before" ]
[ "${lines[2]}" == "before2" ]
[ "${lines[3]}" == "inside precmd" ]
[ "${lines[4]}" == "after" ]
[ "${#lines[@]}" == '5' ]
if __bp_use_array_prompt_command; then
[ "${lines[3]}" == "after" ]
[ "${lines[4]}" == "inside precmd" ]
else
[ "${lines[3]}" == "inside precmd" ]
[ "${lines[4]}" == "after" ]
fi
}
@test "during install PROMPT_COMMAND and precmd functions should be executed each once (Bash 5.1+ PROMPT_COMMAND array)" {
if __bp_use_array_prompt_command; then
PROMPT_COMMAND=("echo before")
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "echo before2")
__bp_install_after_session_init
PROMPT_COMMAND=("${PROMPT_COMMAND[@]}" "echo after")
PROMPT_COMMAND=("echo after2" "${PROMPT_COMMAND[@]}")
precmd() { echo "inside precmd"; }
run eval_PROMPT_COMMAND
[ "${#lines[@]}" == '5' ]
[ "${lines[0]}" == "after2" ]
[ "${lines[1]}" == "before" ]
[ "${lines[2]}" == "before2" ]
[ "${lines[3]}" == "inside precmd" ]
[ "${lines[4]}" == "after" ]
else
skip
fi
}
@test "No functions defined for preexec should simply return" {