From 35762e4d3129d5e49eae4d1a3a9b303d7426db77 Mon Sep 17 00:00:00 2001 From: LanikSJ Date: Mon, 7 Jan 2019 07:38:57 -0800 Subject: [PATCH] Preserve env in script --- lib/preexec.bash | 364 ++++++++++++++++++++++++----------------------- 1 file changed, 183 insertions(+), 181 deletions(-) diff --git a/lib/preexec.bash b/lib/preexec.bash index 5cfcd786..c2c00e0f 100644 --- a/lib/preexec.bash +++ b/lib/preexec.bash @@ -1,3 +1,5 @@ +#!/usr/bin/env + # bash-preexec.sh -- Bash support for ZSH-like 'preexec' and 'precmd' functions. # https://github.com/rcaloras/bash-preexec # @@ -34,7 +36,7 @@ # Avoid duplicate inclusion if [[ "${__bp_imported:-}" == "defined" ]]; then - return 0 + return 0 fi __bp_imported="defined" @@ -65,13 +67,13 @@ __bp_require_not_readonly() { # so we can accurately invoke preexec with a command from our # history even if it starts with a space. __bp_adjust_histcontrol() { - local histcontrol - histcontrol="${HISTCONTROL//ignorespace}" - # Replace ignoreboth with ignoredups - if [[ "$histcontrol" == *"ignoreboth"* ]]; then - histcontrol="ignoredups:${histcontrol//ignoreboth}" - fi; - export HISTCONTROL="$histcontrol" + local histcontrol + histcontrol="${HISTCONTROL//ignorespace}" + # Replace ignoreboth with ignoredups + if [[ "$histcontrol" == *"ignoreboth"* ]]; then + histcontrol="ignoredups:${histcontrol//ignoreboth}" + fi; + export HISTCONTROL="$histcontrol" } # This variable describes whether we are currently in "interactive mode"; @@ -82,75 +84,75 @@ __bp_adjust_histcontrol() { __bp_preexec_interactive_mode="" __bp_trim_whitespace() { - local var=$@ - var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters - var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters - echo -n "$var" + local var=$@ + var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters + var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters + echo -n "$var" } # This function is installed as part of the PROMPT_COMMAND; # It sets a variable to indicate that the prompt was just displayed, # to allow the DEBUG trap to know that the next command is likely interactive. __bp_interactive_mode() { - __bp_preexec_interactive_mode="on"; + __bp_preexec_interactive_mode="on"; } # This function is installed as part of the PROMPT_COMMAND. # It will invoke any functions defined in the precmd_functions array. __bp_precmd_invoke_cmd() { - # Save the returned value from our last command, and from each process in - # its pipeline. Note: this MUST be the first thing done in this function. - __bp_last_ret_value="$?" BP_PIPESTATUS=("${PIPESTATUS[@]}") + # Save the returned value from our last command, and from each process in + # its pipeline. Note: this MUST be the first thing done in this function. + __bp_last_ret_value="$?" BP_PIPESTATUS=("${PIPESTATUS[@]}") - # Don't invoke precmds if we are inside an execution of an "original - # prompt command" by another precmd execution loop. This avoids infinite - # recursion. - if (( __bp_inside_precmd > 0 )); then - return + # Don't invoke precmds if we are inside an execution of an "original + # prompt command" by another precmd execution loop. This avoids infinite + # recursion. + if (( __bp_inside_precmd > 0 )); then + return + fi + local __bp_inside_precmd=1 + + # Invoke every function defined in our function array. + local precmd_function + for precmd_function in "${precmd_functions[@]}"; do + + # Only execute this function if it actually exists. + # Test existence of functions with: declare -[Ff] + if type -t "$precmd_function" 1>/dev/null; then + __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command" + # Quote our function invocation to prevent issues with IFS + "$precmd_function" fi - local __bp_inside_precmd=1 - - # Invoke every function defined in our function array. - local precmd_function - for precmd_function in "${precmd_functions[@]}"; do - - # Only execute this function if it actually exists. - # Test existence of functions with: declare -[Ff] - if type -t "$precmd_function" 1>/dev/null; then - __bp_set_ret_value "$__bp_last_ret_value" "$__bp_last_argument_prev_command" - # Quote our function invocation to prevent issues with IFS - "$precmd_function" - fi - done + done } # Sets a return value in $?. We may want to get access to the $? variable in our # precmd functions. This is available for instance in zsh. We can simulate it in bash # by setting the value here. __bp_set_ret_value() { - return ${1:-} + return ${1:-} } __bp_in_prompt_command() { - local prompt_command_array - IFS=';' read -ra prompt_command_array <<< "$PROMPT_COMMAND" + local prompt_command_array + IFS=';' read -ra prompt_command_array <<< "$PROMPT_COMMAND" - local trimmed_arg - trimmed_arg=$(__bp_trim_whitespace "${1:-}") + local trimmed_arg + trimmed_arg=$(__bp_trim_whitespace "${1:-}") - local command - for command in "${prompt_command_array[@]:-}"; do - local trimmed_command - trimmed_command=$(__bp_trim_whitespace "$command") - # Only execute each function if it actually exists. - if [[ "$trimmed_command" == "$trimmed_arg" ]]; then - return 0 - fi - done + local command + for command in "${prompt_command_array[@]:-}"; do + local trimmed_command + trimmed_command=$(__bp_trim_whitespace "$command") + # Only execute each function if it actually exists. + if [[ "$trimmed_command" == "$trimmed_arg" ]]; then + return 0 + fi + done - return 1 + return 1 } # This function is installed as the DEBUG trap. It is invoked before each @@ -158,133 +160,133 @@ __bp_in_prompt_command() { # environment to attempt to detect if the current command is being invoked # interactively, and invoke 'preexec' if so. __bp_preexec_invoke_exec() { - # Save the contents of $_ so that it can be restored later on. - # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702 - __bp_last_argument_prev_command="${1:-}" - # Don't invoke preexecs if we are inside of another preexec. - if (( __bp_inside_preexec > 0 )); then - return + # Save the contents of $_ so that it can be restored later on. + # https://stackoverflow.com/questions/40944532/bash-preserve-in-a-debug-trap#40944702 + __bp_last_argument_prev_command="${1:-}" + # Don't invoke preexecs if we are inside of another preexec. + if (( __bp_inside_preexec > 0 )); then + return + fi + local __bp_inside_preexec=1 + + # Checks if the file descriptor is not standard out (i.e. '1') + # __bp_delay_install checks if we're in test. Needed for bats to run. + # Prevents preexec from being invoked for functions in PS1 + if [[ ! -t 1 && -z "${__bp_delay_install:-}" ]]; then + return + fi + + if [[ -n "${COMP_LINE:-}" ]]; then + # We're in the middle of a completer. This obviously can't be + # an interactively issued command. + return + fi + if [[ -z "${__bp_preexec_interactive_mode:-}" ]]; then + # We're doing something related to displaying the prompt. Let the + # prompt set the title instead of me. + return + else + # If we're in a subshell, then the prompt won't be re-displayed to put + # us back into interactive mode, so let's not set the variable back. + # In other words, if you have a subshell like + # (sleep 1; sleep 2) + # You want to see the 'sleep 2' as a set_command_title as well. + if [[ 0 -eq "${BASH_SUBSHELL:-}" ]]; then + __bp_preexec_interactive_mode="" fi - local __bp_inside_preexec=1 + fi - # Checks if the file descriptor is not standard out (i.e. '1') - # __bp_delay_install checks if we're in test. Needed for bats to run. - # Prevents preexec from being invoked for functions in PS1 - if [[ ! -t 1 && -z "${__bp_delay_install:-}" ]]; then - return + if __bp_in_prompt_command "${BASH_COMMAND:-}"; then + # If we're executing something inside our prompt_command then we don't + # want to call preexec. Bash prior to 3.1 can't detect this at all :/ + __bp_preexec_interactive_mode="" + return + fi + + local this_command + this_command=$(HISTTIMEFORMAT= builtin history 1 | sed '1 s/^ *[0-9]\+[* ] //') + + # Sanity check to make sure we have something to invoke our function with. + if [[ -z "$this_command" ]]; then + return + fi + + # If none of the previous checks have returned out of this function, then + # the command is in fact interactive and we should invoke the user's + # preexec functions. + + # Invoke every function defined in our function array. + local preexec_function + local preexec_function_ret_value + local preexec_ret_value=0 + for preexec_function in "${preexec_functions[@]:-}"; do + + # Only execute each function if it actually exists. + # Test existence of function with: declare -[fF] + if type -t "$preexec_function" 1>/dev/null; then + __bp_set_ret_value ${__bp_last_ret_value:-} + # Quote our function invocation to prevent issues with IFS + "$preexec_function" "$this_command" + preexec_function_ret_value="$?" + if [[ "$preexec_function_ret_value" != 0 ]]; then + preexec_ret_value="$preexec_function_ret_value" + fi fi + done - if [[ -n "${COMP_LINE:-}" ]]; then - # We're in the middle of a completer. This obviously can't be - # an interactively issued command. - return - fi - if [[ -z "${__bp_preexec_interactive_mode:-}" ]]; then - # We're doing something related to displaying the prompt. Let the - # prompt set the title instead of me. - return - else - # If we're in a subshell, then the prompt won't be re-displayed to put - # us back into interactive mode, so let's not set the variable back. - # In other words, if you have a subshell like - # (sleep 1; sleep 2) - # You want to see the 'sleep 2' as a set_command_title as well. - if [[ 0 -eq "${BASH_SUBSHELL:-}" ]]; then - __bp_preexec_interactive_mode="" - fi - fi - - if __bp_in_prompt_command "${BASH_COMMAND:-}"; then - # If we're executing something inside our prompt_command then we don't - # want to call preexec. Bash prior to 3.1 can't detect this at all :/ - __bp_preexec_interactive_mode="" - return - fi - - local this_command - this_command=$(HISTTIMEFORMAT= builtin history 1 | sed '1 s/^ *[0-9]\+[* ] //') - - # Sanity check to make sure we have something to invoke our function with. - if [[ -z "$this_command" ]]; then - return - fi - - # If none of the previous checks have returned out of this function, then - # the command is in fact interactive and we should invoke the user's - # preexec functions. - - # Invoke every function defined in our function array. - local preexec_function - local preexec_function_ret_value - local preexec_ret_value=0 - for preexec_function in "${preexec_functions[@]:-}"; do - - # Only execute each function if it actually exists. - # Test existence of function with: declare -[fF] - if type -t "$preexec_function" 1>/dev/null; then - __bp_set_ret_value ${__bp_last_ret_value:-} - # Quote our function invocation to prevent issues with IFS - "$preexec_function" "$this_command" - preexec_function_ret_value="$?" - if [[ "$preexec_function_ret_value" != 0 ]]; then - preexec_ret_value="$preexec_function_ret_value" - fi - fi - done - - # Restore the last argument of the last executed command, and set the return - # value of the DEBUG trap to be the return code of the last preexec function - # to return an error. - # If `extdebug` is enabled a non-zero return value from any preexec function - # will cause the user's command not to execute. - # Run `shopt -s extdebug` to enable - __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command" + # Restore the last argument of the last executed command, and set the return + # value of the DEBUG trap to be the return code of the last preexec function + # to return an error. + # If `extdebug` is enabled a non-zero return value from any preexec function + # will cause the user's command not to execute. + # Run `shopt -s extdebug` to enable + __bp_set_ret_value "$preexec_ret_value" "$__bp_last_argument_prev_command" } __bp_install() { - # Exit if we already have this installed. - if [[ "${PROMPT_COMMAND:-}" == *"__bp_precmd_invoke_cmd"* ]]; then - return 1; - fi + # Exit if we already have this installed. + if [[ "${PROMPT_COMMAND:-}" == *"__bp_precmd_invoke_cmd"* ]]; then + return 1; + fi - trap '__bp_preexec_invoke_exec "$_"' DEBUG + trap '__bp_preexec_invoke_exec "$_"' DEBUG - # Preserve any prior DEBUG trap as a preexec function - local prior_trap=$(sed "s/[^']*'\(.*\)'[^']*/\1/" <<<"${__bp_trap_string:-}") - unset __bp_trap_string - if [[ -n "$prior_trap" ]]; then - eval '__bp_original_debug_trap() { + # Preserve any prior DEBUG trap as a preexec function + local prior_trap=$(sed "s/[^']*'\(.*\)'[^']*/\1/" <<<"${__bp_trap_string:-}") + unset __bp_trap_string + if [[ -n "$prior_trap" ]]; then + eval '__bp_original_debug_trap() { '"$prior_trap"' - }' - preexec_functions+=(__bp_original_debug_trap) - fi + }' + preexec_functions+=(__bp_original_debug_trap) + fi - # Adjust our HISTCONTROL Variable if needed. - __bp_adjust_histcontrol + # Adjust our HISTCONTROL Variable if needed. + __bp_adjust_histcontrol - # Issue #25. Setting debug trap for subshells causes sessions to exit for - # backgrounded subshell commands (e.g. (pwd)& ). Believe this is a bug in Bash. - # - # Disabling this by default. It can be enabled by setting this variable. - if [[ -n "${__bp_enable_subshells:-}" ]]; then + # Issue #25. Setting debug trap for subshells causes sessions to exit for + # backgrounded subshell commands (e.g. (pwd)& ). Believe this is a bug in Bash. + # + # Disabling this by default. It can be enabled by setting this variable. + if [[ -n "${__bp_enable_subshells:-}" ]]; then - # Set so debug trap will work be invoked in subshells. - set -o functrace > /dev/null 2>&1 - shopt -s extdebug > /dev/null 2>&1 - fi; + # Set so debug trap will work be invoked in subshells. + set -o functrace > /dev/null 2>&1 + shopt -s extdebug > /dev/null 2>&1 + 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; __bp_interactive_mode" + # Install our hooks in PROMPT_COMMAND to allow our trap to know when we've + # actually entered something. + PROMPT_COMMAND="__bp_precmd_invoke_cmd; __bp_interactive_mode" - # Add two functions to our arrays for convenience - # of definition. - precmd_functions+=(precmd) - preexec_functions+=(preexec) + # Add two functions to our arrays for convenience + # of definition. + precmd_functions+=(precmd) + preexec_functions+=(preexec) - # Since this function is invoked via PROMPT_COMMAND, re-execute PC now that it's properly set - eval "$PROMPT_COMMAND" + # Since this function is invoked via PROMPT_COMMAND, re-execute PC now that it's properly set + eval "$PROMPT_COMMAND" } # Sets our trap and __bp_install as part of our PROMPT_COMMAND to install @@ -294,33 +296,33 @@ __bp_install() { # the function. __bp_install_after_session_init() { - # Make sure this is bash that's running this and return otherwise. - if [[ -z "${BASH_VERSION:-}" ]]; then - return 1; - fi + # Make sure this is bash that's running this and return otherwise. + if [[ -z "${BASH_VERSION:-}" ]]; then + return 1; + fi - # bash-preexec needs to modify these variables in order to work correctly - # if it can't, just stop the installation - __bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return + # bash-preexec needs to modify these variables in order to work correctly + # if it can't, just stop the installation + __bp_require_not_readonly PROMPT_COMMAND HISTCONTROL HISTTIMEFORMAT || return - # If there's an existing PROMPT_COMMAND capture it and convert it into a function - # So it is preserved and invoked during precmd. - if [[ -n "$PROMPT_COMMAND" ]]; then - eval '__bp_original_prompt_command() { + # If there's an existing PROMPT_COMMAND capture it and convert it into a function + # So it is preserved and invoked during precmd. + if [[ -n "$PROMPT_COMMAND" ]]; then + eval '__bp_original_prompt_command() { '"$PROMPT_COMMAND"' - }' - precmd_functions+=(__bp_original_prompt_command) - fi + }' + precmd_functions+=(__bp_original_prompt_command) + fi - # Installation is finalized in PROMPT_COMMAND, which allows us to override the DEBUG - # trap. __bp_install sets PROMPT_COMMAND to its final value, so these are only - # invoked once. - # It's necessary to clear any existing DEBUG trap in order to set it from the install function. - # Using \n as it's the most universal delimiter of bash commands - PROMPT_COMMAND=$'\n__bp_trap_string="$(trap -p DEBUG)"\ntrap DEBUG\n__bp_install\n' + # Installation is finalized in PROMPT_COMMAND, which allows us to override the DEBUG + # trap. __bp_install sets PROMPT_COMMAND to its final value, so these are only + # invoked once. + # It's necessary to clear any existing DEBUG trap in order to set it from the install function. + # Using \n as it's the most universal delimiter of bash commands + PROMPT_COMMAND=$'\n__bp_trap_string="$(trap -p DEBUG)"\ntrap DEBUG\n__bp_install\n' } # Run our install so long as we're not delaying it. if [[ -z "$__bp_delay_install" ]]; then - __bp_install_after_session_init + __bp_install_after_session_init fi;