In bash, can I arrange for a function to be executed just before running a command?

There is $PROMPT_COMMAND, which is executed before showing a prompt, i.e., just after running a command.

Bash's $PROMPT_COMMAND is analogous to zsh's precmd function; so what I'm looking for is a bash equivalent to zsh's preexec.

Example applications: set your terminal title to the command being executed; automatically add time before every command.

link|improve this question
feedback

3 Answers

up vote 12 down vote accepted

Not natively, but it can be hacked up using the DEBUG trap. This code sets up preexec and precmd functions similar to zsh. The command line is passed as a single argument to preexec.

Here is a simplified version of the code to set up a precmd function that is executed before running each command.

preexec () { :; }
preexec_invoke_exec () {
    [ -n "$COMP_LINE" ] && return  # do nothing if completing
    local this_command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
    preexec "$this_command"
}
trap 'preexec_invoke_exec' DEBUG

This trick is due to Glyph Lefkowitz; thanks to bcat for locating the original author.

http://www.macosxhints.com/dlfiles/preexec.bash.txt

link|improve this answer
2  
FYI: This code is by Glyph Lefkowitz and the original source can be found here: glyf.livejournal.com/63106.html – bcat Jan 2 '11 at 6:31
Pinging @bcat again. He's active on SO, maybe he sees this. – Daniel Beck Jan 26 '11 at 15:19
feedback

You can use the trap command (from help trap):

If a SIGNAL_SPEC is DEBUG, ARG is executed before every simple command.

For example, to change the terminal title dynamically you may use:

trap 'echo -e "\e]0;$BASH_COMMAND\007"' DEBUG

From this source.

link|improve this answer
feedback

Thank you for the hints! I ended up using this:

 #created by francois scheurer

 #sourced by '~/.bashrc', which is the last runned startup script for bash invocation
 #for login interactive, login non-interactive and non-login interactive shells.
 #note that a user can easily avoid calling this file by using options like '--norc';
 #he also can unset or overwrite variables like 'PROMPT_COMMAND'.
 #therefore it is useful for audit but not for security.

 #prompt & color
 #http://www.pixelbeat.org/docs/terminal_colours/#256
 #http://www.frexx.de/xterm-256-notes/
 _backnone="\e[00m"
 _backblack="\e[40m"
 _backblue="\e[44m"
 _frontred_b="\e[01;31m"
 _frontgreen_b="\e[01;32m"
 _frontgrey_b="\e[01;37m"
 _frontgrey="\e[00;37m"
 _frontblue_b="\e[01;34m"
 PS1="\[${_backblue}${_frontgreen_b}\]\u@\h:\[${_backblack}${_frontblue_b}\]\w\\$\[${_backnone}${_frontgreen_b}\] "

 #'history' options
 declare -rx HISTFILE="$HOME/.bash_history"
 chattr +a "$HISTFILE" # set append-only
 declare -rx HISTSIZE=500000 #nbr of cmds in memory
 declare -rx HISTFILESIZE=500000 #nbr of cmds on file
 declare -rx HISTCONTROL="" #does not ignore spaces or duplicates
 declare -rx HISTIGNORE="" #does not ignore patterns
 declare -rx HISTCMD #history line number
 history -r #to reload history from file if a prior HISTSIZE has truncated it
 if groups | grep -q root; then declare -x TMOUT=3600; fi #timeout for root's sessions

 #enable forward search (ctrl-s)
 #http://ruslanspivak.com/2010/11/25/bash-history-incremental-search-forward/
 stty -ixon

 #history substitution ask for a confirmation
 shopt -s histverify

 #add timestamps in history - obsoleted with logger/syslog
 #http://www.thegeekstuff.com/2008/08/15-examples-to-master-linux-command-line-history/#more-130
 #declare -rx HISTTIMEFORMAT='%F %T '

 #bash audit & traceabilty
 #
 #
 declare -rx AUDIT_LOGINUSER="$(who -mu | awk '{print $1}')"
 declare -rx AUDIT_LOGINPID="$(who -mu | awk '{print $6}')"
 declare -rx AUDIT_USER="$USER" #defined by pam during su/sudo
 declare -rx AUDIT_PID="$$"
 declare -rx AUDIT_TTY="$(who -mu | awk '{print $2}')"
 declare -rx AUDIT_SSH="$([ -n "$SSH_CONNECTION" ] && echo "$SSH_CONNECTION" | awk '{print $1":"$2"->"$3":"$4}')"
 declare -rx AUDIT_STR="[audit $AUDIT_LOGINUSER/$AUDIT_LOGINPID as $AUDIT_USER/$AUDIT_PID on $AUDIT_TTY/$AUDIT_SSH]"
 #
 #PROMPT_COMMAND solution is working but the syslog message are sent *after* the command execution, 
 #this causes 'su' or 'sudo' commands to appear only after logouts, and 'cd' commands to display wrong working directory
 #http://jablonskis.org/2011/howto-log-bash-history-to-syslog/
 #declare -rx PROMPT_COMMAND='history -a >(tee -a ~/.bash_history | logger -p user.info -t "$AUDIT_STR $PWD")' #avoid subshells here or duplicate execution will occurs!
 #
 #another solution is to use 'trap' DEBUG, which is executed *before* the command.
 #http://superuser.com/questions/175799/does-bash-have-a-hook-that-is-run-before-executing-a-command
 #http://www.davidpashley.com/articles/xterm-titles-with-bash.html
 #set -o functrace; trap 'echo -ne "===$BASH_COMMAND===${_backvoid}${_frontgrey}\n"' DEBUG
 set +o functrace #disable trap DEBUG inherited in functions, command substitutions or subshells, normally the default setting already
 #function audit_DEBUG() {
 #  echo -ne "${_backnone}${_frontgrey}"
 #  (history -a >(logger -p user.info -t "$AUDIT_STR $PWD" < <(tee -a ~/.bash_history))) && sync && history -c && history -r
 #  #http://stackoverflow.com/questions/103944/real-time-history-export-amongst-bash-terminal-windows
 #  #'history -c && history -r' force a refresh of the history because 'history -a' was called within a subshell and therefore
 #  #the new history commands that are appent to file will keep their "new" status outside of the subshell, causing their logging
 #  #to re-occur on every function call...
 #  #note that without the subshell, piped bash commands would hang... (it seems that the trap + process substitution interfer with stdin redirection)
 #  #and with the subshell
 #}
 ##enable trap DEBUG inherited for all subsequent functions; required to audit commands beginning with the char '(' for a subshell
 #set -o functrace #=> problem: completion in commands avoid logging them
 function audit_DEBUG() {
   #simplier and quicker version! avoid 'sync' and 'history -r' that are time consuming!
   if [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ] #avoid logging unexecuted commands after Ctrl-C or Empty+Enter
   then
     echo -ne "${_backnone}${_frontgrey}"
     local AUDIT_CMD="$(history 1)" #current history command
     #remove in last history cmd its line number (if any) and send to syslog
     if ! logger -p user.info -t "$AUDIT_STR $PWD" "${AUDIT_CMD##*( )?(+([0-9])[^0-9])*( )}"
     then
       echo error "$AUDIT_STR $PWD" "${AUDIT_CMD##*( )?(+([0-9])[^0-9])*( )}"
     fi
   fi
 #echo "===cmd:$BASH_COMMAND/subshell:$BASH_SUBSHELL/fc:$(fc -l -1)/history:$(history 1)/histline:${AUDIT_CMD%%+([^ 0-9])*}===" #for debugging
 }
 function audit_EXIT() {
   local AUDIT_STATUS="$?"
   logger -p user.info -t "$AUDIT_STR" "#=== bash session ended. ==="
   exit "$AUDIT_STATUS"
 }
 #make audit trap functions readonly; disable trap DEBUG inherited (normally the default setting already)
 declare -fr +t audit_DEBUG
 declare -fr +t audit_EXIT
 logger -p user.info -t "$AUDIT_STR" "#=== New bash session started. ===" #audit the session openning
 #when a bash command is executed it launches first the audit_DEBUG(),
 #then the trap DEBUG is disabled to avoid a useless rerun of audit_DEBUG() during the execution of pipes-commands;
 #at the end, when the prompt is displayed, re-enable the trap DEBUG
 declare -rx PROMPT_COMMAND="trap 'audit_DEBUG; trap DEBUG' DEBUG"
 declare -rx BASH_COMMAND #current command executed by user or a trap
 declare -rx SHELLOPT #shell options, like functrace  
 trap audit_EXIT EXIT #audit the session closing

Enjoy!

link|improve this answer
I had a problem with piped bash commands that hangs... I found a workaround using a subshell, but this caused the 'history -a' to not refresh the history outside the subshell scope... Finally the solution was to use a function that re-read the history after the subshell execution. It works as I wanted. As Vaidas wrote on jablonskis.org/2011/howto-log-bash-history-to-syslog, it is more easy to deploy than patching the bash in C (i did that too in the past). but there is some performance drop while re-reading each time the history file and doing a disk 'sync'... – francois scheurer Feb 7 at 22:18
You might want to trim that code; currently it's almost completely unreadable. – l0b0 Mar 22 at 11:27
feedback

Your Answer

 
or
required, but never shown

Not the answer you're looking for? Browse other questions tagged or ask your own question.