You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bash_preexec 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #!/bin/bash
  2. #
  3. # bash-preexec.sh -- Bash support for ZSH-like 'preexec' and 'precmd' functions.
  4. # https://github.com/rcaloras/bash-preexec
  5. #
  6. #
  7. # 'preexec' functions are executed before each interactive command is
  8. # executed, with the interactive command as its argument. The 'precmd'
  9. # function is executed before each prompt is displayed.
  10. #
  11. # Author: Ryan Caloras (ryan@bashhub.com)
  12. # Forked from Original Author: Glyph Lefkowitz
  13. #
  14. # V0.2.1
  15. #
  16. # General Usage:
  17. #
  18. # 1. Source this file at the end of your bash profile so as not to interfere
  19. # with anything else that's using PROMPT_COMMAND.
  20. #
  21. # 2. Add any precmd or preexec functions by appending them to their arrays:
  22. # e.g.
  23. # precmd_functions+=(my_precmd_function)
  24. # precmd_functions+=(some_other_precmd_function)
  25. #
  26. # preexec_functions+=(my_preexec_function)
  27. #
  28. # 3. If you have anything that's using the Debug Trap, change it to use
  29. # preexec. (Optional) change anything using PROMPT_COMMAND to now use
  30. # precmd instead.
  31. #
  32. # Note: This module requires two bash features which you must not otherwise be
  33. # using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. prexec_and_precmd_install
  34. # will override these and if you override one or the other this will most likely break.
  35. # Avoid duplicate inclusion
  36. if [[ "$__bp_imported" == "defined" ]]; then
  37. return 0
  38. fi
  39. __bp_imported="defined"
  40. # Remove ignorespace and or replace ignoreboth from HISTCONTROL
  41. # so we can accurately invoke preexec with a command from our
  42. # history even if it starts with a space.
  43. __bp_adjust_histcontrol() {
  44. local histcontrol
  45. histcontrol="${HISTCONTROL//ignorespace}"
  46. # Replace ignoreboth with ignoredups
  47. if [[ "$histcontrol" == *"ignoreboth"* ]]; then
  48. histcontrol="ignoredups:${histcontrol//ignoreboth}"
  49. fi;
  50. export HISTCONTROL="$histcontrol"
  51. }
  52. # This variable describes whether we are currently in "interactive mode";
  53. # i.e. whether this shell has just executed a prompt and is waiting for user
  54. # input. It documents whether the current command invoked by the trace hook is
  55. # run interactively by the user; it's set immediately after the prompt hook,
  56. # and unset as soon as the trace hook is run.
  57. __bp_preexec_interactive_mode=""
  58. __bp_trim_whitespace() {
  59. local var=$@
  60. var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
  61. var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
  62. echo -n "$var"
  63. }
  64. # This function is installed as part of the PROMPT_COMMAND;
  65. # It sets a variable to indicate that the prompt was just displayed,
  66. # to allow the DEBUG trap to know that the next command is likely interactive.
  67. __bp_interactive_mode() {
  68. __bp_preexec_interactive_mode="on";
  69. }
  70. # This function is installed as part of the PROMPT_COMMAND.
  71. # It will invoke any functions defined in the precmd_functions array.
  72. __bp_precmd_invoke_cmd() {
  73. # Should be available to each precmd function, should it want it.
  74. local ret_value="$?"
  75. # For every function defined in our function array. Invoke it.
  76. local precmd_function
  77. for precmd_function in ${precmd_functions[@]}; do
  78. # Only execute this function if it actually exists.
  79. if [[ -n $(type -t $precmd_function) ]]; then
  80. __bp_set_ret_value $ret_value
  81. $precmd_function
  82. fi
  83. done
  84. }
  85. # Sets a return value in $?. We may want to get access to the $? variable in our
  86. # precmd functions. This is available for instance in zsh. We can simulate it in bash
  87. # by setting the value here.
  88. __bp_set_ret_value() {
  89. return $1
  90. }
  91. __bp_in_prompt_command() {
  92. local prompt_command_array
  93. IFS=';' read -ra prompt_command_array <<< "$PROMPT_COMMAND"
  94. local trimmed_arg
  95. trimmed_arg=$(__bp_trim_whitespace "$1")
  96. local prompt_command_function
  97. for command in "${prompt_command_array[@]}"; do
  98. local trimmed_command
  99. trimmed_command=$(__bp_trim_whitespace "$command")
  100. # Only execute each function if it actually exists.
  101. if [[ "$trimmed_command" == "$trimmed_arg" ]]; then
  102. return 0
  103. fi
  104. done
  105. return 1
  106. }
  107. # This function is installed as the DEBUG trap. It is invoked before each
  108. # interactive prompt display. Its purpose is to inspect the current
  109. # environment to attempt to detect if the current command is being invoked
  110. # interactively, and invoke 'preexec' if so.
  111. __bp_preexec_invoke_exec() {
  112. if [[ -n "$COMP_LINE" ]]
  113. then
  114. # We're in the middle of a completer. This obviously can't be
  115. # an interactively issued command.
  116. return
  117. fi
  118. if [[ -z "$__bp_preexec_interactive_mode" ]]
  119. then
  120. # We're doing something related to displaying the prompt. Let the
  121. # prompt set the title instead of me.
  122. return
  123. else
  124. # If we're in a subshell, then the prompt won't be re-displayed to put
  125. # us back into interactive mode, so let's not set the variable back.
  126. # In other words, if you have a subshell like
  127. # (sleep 1; sleep 2)
  128. # You want to see the 'sleep 2' as a set_command_title as well.
  129. if [[ 0 -eq "$BASH_SUBSHELL" ]]
  130. then
  131. __bp_preexec_interactive_mode=""
  132. fi
  133. fi
  134. if __bp_in_prompt_command "$BASH_COMMAND"; then
  135. # If we're executing something inside our prompt_command then we don't
  136. # want to call preexec. Bash prior to 3.1 can't detect this at all :/
  137. __bp_preexec_interactive_mode=""
  138. return
  139. fi
  140. local this_command="$(HISTTIMEFORMAT= history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g")";
  141. # Sanity check to make sure we have something to invoke our function with.
  142. if [[ -z "$this_command" ]]; then
  143. return
  144. fi
  145. # If none of the previous checks have returned out of this function, then
  146. # the command is in fact interactive and we should invoke the user's
  147. # preexec functions.
  148. # For every function defined in our function array. Invoke it.
  149. local preexec_function
  150. for preexec_function in "${preexec_functions[@]}"; do
  151. # Only execute each function if it actually exists.
  152. if [[ -n $(type -t $preexec_function) ]]; then
  153. $preexec_function "$this_command"
  154. fi
  155. done
  156. }
  157. # Execute this to set up preexec and precmd execution.
  158. __bp_preexec_and_precmd_install() {
  159. # Make sure this is bash that's running this and return otherwise.
  160. if [[ -z "$BASH_VERSION" ]]; then
  161. return 1;
  162. fi
  163. # Exit if we already have this installed.
  164. if [[ "$PROMPT_COMMAND" == *"__bp_precmd_invoke_cmd"* ]]; then
  165. return 1;
  166. fi
  167. # Adjust our HISTCONTROL Variable if needed.
  168. __bp_adjust_histcontrol
  169. # Take our existing prompt command and append a semicolon to it
  170. # if it doesn't already have one.
  171. local existing_prompt_command
  172. if [[ -n "$PROMPT_COMMAND" ]]; then
  173. existing_prompt_command=$(echo "$PROMPT_COMMAND" | sed '/; *$/!s/$/;/')
  174. else
  175. existing_prompt_command=""
  176. fi
  177. # Finally install our traps.
  178. PROMPT_COMMAND="__bp_precmd_invoke_cmd; ${existing_prompt_command} __bp_interactive_mode;"
  179. trap '__bp_preexec_invoke_exec' DEBUG;
  180. # Add two functions to our arrays for convenience
  181. # of definition.
  182. precmd_functions+=(precmd)
  183. preexec_functions+=(preexec)
  184. }
  185. # Run our install so long as we're not delaying it.
  186. if [[ -z "$__bp_delay_install" ]]; then
  187. __bp_preexec_and_precmd_install
  188. fi;