From 7ad48e14d3939ea854100781406a8fc2707c8fa1 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 12 Mar 2023 17:27:10 -0700 Subject: [PATCH] Support the array PROMPT_COMMAND in Bash 5.1+ (#141) --- bash-preexec.sh | 24 ++++++++++++++++-------- test/bash-preexec.bats | 32 +++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/bash-preexec.sh b/bash-preexec.sh index 4554cf8..bc98976 100644 --- a/bash-preexec.sh +++ b/bash-preexec.sh @@ -183,8 +183,8 @@ __bp_set_ret_value() { __bp_in_prompt_command() { - local prompt_command_array - IFS=$'\n;' read -rd '' -a prompt_command_array <<< "${PROMPT_COMMAND:-}" + local prompt_command_array IFS=$'\n;' + read -rd '' -a prompt_command_array <<< "${PROMPT_COMMAND[*]:-}" local trimmed_arg __bp_trim_whitespace trimmed_arg "${1:-}" @@ -290,7 +290,7 @@ __bp_preexec_invoke_exec() { __bp_install() { # Exit if we already have this installed. - if [[ "${PROMPT_COMMAND:-}" == *"__bp_precmd_invoke_cmd"* ]]; then + if [[ "${PROMPT_COMMAND[*]:-}" == *"__bp_precmd_invoke_cmd"* ]]; then return 1; fi @@ -331,14 +331,20 @@ __bp_install() { existing_prompt_command="${existing_prompt_command//$'\n':$'\n'/$'\n'}" # remove known-token only existing_prompt_command="${existing_prompt_command//$'\n':;/$'\n'}" # remove known-token only __bp_sanitize_string existing_prompt_command "$existing_prompt_command" + if [[ "${existing_prompt_command:-:}" == ":" ]]; then + existing_prompt_command= + fi # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've # actually entered something. - PROMPT_COMMAND=$'__bp_precmd_invoke_cmd\n' - if [[ "${existing_prompt_command:-:}" != ":" ]]; then - PROMPT_COMMAND+=${existing_prompt_command}$'\n' - fi; - PROMPT_COMMAND+='__bp_interactive_mode' + PROMPT_COMMAND='__bp_precmd_invoke_cmd' + PROMPT_COMMAND+=${existing_prompt_command:+$'\n'$existing_prompt_command} + if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1) )); then + PROMPT_COMMAND+=('__bp_interactive_mode') + else + # shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0 + PROMPT_COMMAND+=$'\n__bp_interactive_mode' + fi # Add two functions to our arrays for convenience # of definition. @@ -361,8 +367,10 @@ __bp_install_after_session_init() { 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} } diff --git a/test/bash-preexec.bats b/test/bash-preexec.bats index b8d783c..3d3f6b7 100644 --- a/test/bash-preexec.bats +++ b/test/bash-preexec.bats @@ -8,9 +8,23 @@ setup() { source "${BATS_TEST_DIRNAME}/../bash-preexec.sh" } +# Evaluates all the elements of PROMPT_COMMAND +eval_PROMPT_COMMAND() { + local prompt_command + for prompt_command in "${PROMPT_COMMAND[@]}"; do + eval "$prompt_command" + done +} + +# Joins the elements of PROMPT_COMMAND with $'\n' +join_PROMPT_COMMAND() { + local IFS=$'\n' + echo "${PROMPT_COMMAND[*]}" +} + bp_install() { __bp_install_after_session_init - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND } test_echo() { @@ -63,7 +77,7 @@ set_exit_code_and_run_precmd() { [[ "$PROMPT_COMMAND" == *"trap - DEBUG"* ]] || return 1 [[ "$PROMPT_COMMAND" == *"__bp_install"* ]] || return 1 - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND [[ "$PROMPT_COMMAND" != *"trap DEBUG"* ]] || return 1 [[ "$PROMPT_COMMAND" != *"__bp_install"* ]] || return 1 @@ -106,7 +120,7 @@ set_exit_code_and_run_precmd() { bp_install PROMPT_COMMAND="$PROMPT_COMMAND; true" - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND } @test "Appending or prepending to PROMPT_COMMAND should work after bp_install_after_session_init" { @@ -119,7 +133,7 @@ set_exit_code_and_run_precmd() { PROMPT_COMMAND="true; $PROMPT_COMMAND" PROMPT_COMMAND="true; $PROMPT_COMMAND" PROMPT_COMMAND="true $nl $PROMPT_COMMAND" - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND } # Case where a user is appending or prepending to PROMPT_COMMAND. @@ -132,10 +146,10 @@ set_exit_code_and_run_precmd() { PROMPT_COMMAND="$PROMPT_COMMAND"$'\n echo after' PROMPT_COMMAND="echo after2; $PROMPT_COMMAND;" - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND expected_result=$'__bp_precmd_invoke_cmd\necho after2; echo before; echo before2\n echo after\n__bp_interactive_mode' - [ "$PROMPT_COMMAND" == "$expected_result" ] + [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] } @test "Adding to PROMPT_COMMAND after with semicolon" { @@ -143,10 +157,10 @@ set_exit_code_and_run_precmd() { __bp_install_after_session_init PROMPT_COMMAND="$PROMPT_COMMAND; echo after" - eval "$PROMPT_COMMAND" + eval_PROMPT_COMMAND expected_result=$'__bp_precmd_invoke_cmd\necho before\n echo after\n__bp_interactive_mode' - [ "$PROMPT_COMMAND" == "$expected_result" ] + [ "$(join_PROMPT_COMMAND)" == "$expected_result" ] } @test "during install PROMPT_COMMAND and precmd functions should be executed each once" { @@ -157,7 +171,7 @@ set_exit_code_and_run_precmd() { PROMPT_COMMAND="echo after2; $PROMPT_COMMAND;" precmd() { echo "inside precmd"; } - run eval "$PROMPT_COMMAND" + run eval_PROMPT_COMMAND [ "${lines[0]}" == "after2" ] [ "${lines[1]}" == "before" ] [ "${lines[2]}" == "before2" ]