134 votes

Est-ce que bash a un hook qui est lancé avant l'exécution d'une commande ?

Dans bash, puis-je faire en sorte qu'une fonction soit exécutée juste avant de lancer une commande ?

Il y a $PROMPT_COMMAND qui est exécuté avant l'affichage d'une invite, c'est-à-dire juste après l'exécution d'une commande.

Bash's $PROMPT_COMMAND est analogue à celui de zsh precmd donc ce que je cherche, c'est un équivalent en bash de la fonction de zsh preexec .

Exemples d'applications : définir le titre de votre terminal en fonction de la commande en cours d'exécution ; ajouter automatiquement l'option time avant chaque commande.

7 votes

La version 4.4 de bash possède un PS0 qui agit comme une variable PS1 mais est utilisé après la lecture de la commande mais avant son exécution. Voir gnu.org/software/bash/manual/bashref.html#Bash-Variables

0 votes

PS0 peut être utilisé pour exécuter une commande comme celle de zsh precmd également - par exemple PS0='$(my_precmd)' . Pour que l'invite y une couleur différente de celle de la sortie (par exemple green=ansi code 32), active la couleur verte dans l'invite, PS1='\[\e[32m\] \$ ' puis l'éteindre juste avant l'exécution de la commande avec PS0='\[\e[0m\]' .

107voto

Pas de façon native, mais il est possible de l'améliorer en utilisant la fonction DEBUG piège. Ce code met en place preexec et precmd fonctionne de manière similaire à zsh. La ligne de commande est passée comme un seul argument à preexec .

Voici une version simplifiée du code permettant de mettre en place un fichier de type preexec qui est exécutée avant de lancer chaque commande.

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

Cette astuce est due à Glyph Lefkowitz ; merci à bcat pour retrouver l'auteur original.

Edit. Une version mise à jour du hack de Glyph peut être trouvée ici : https://github.com/rcaloras/bash-preexec

0 votes

El "$BASH_COMMAND" = "$PROMPT_COMMAND" La comparaison ne fonctionne pas pour moi i.imgur.com/blneCdQ.png

2 votes

J'ai essayé d'utiliser ce code sur cygwin. Malheureusement, il a des effets assez intenses sur les performances - en lançant une simple commande de benchmarking time for i in {1..10}; do true; done prend 0,040 seconde en temps normal et 1,400 à 1,600 seconde après l'activation du piège DEBUG. La commande trap doit être exécutée deux fois par boucle - et sur Cygwin, la bifurcation nécessaire à l'exécution de sed est excessivement lente : environ 0,030 seconde pour la bifurcation seule (différence de vitesse entre echo builtin et /bin/echo ). Quelque chose à garder à l'esprit peut-être.

2 votes

@kdb Les performances de Cygwin pour fork sont nulles. Je crois savoir que c'est inévitable sous Windows. Si vous avez besoin d'exécuter du code bash sous Windows, essayez de réduire le nombre de bifurcations.

27voto

cYrus Points 20338

Vous pouvez utiliser le trap (de help trap ) :

Si un SIGNAL_SPEC est DEBUG, ARG est exécuté avant chaque commande simple.

Par exemple, pour changer le titre du terminal de façon dynamique, vous pouvez utiliser :

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

から こん source.

1 votes

Intéressant ... sur mon vieux serveur Ubuntu, help trap dit "Si un SIGNAL_SPEC est DEBUG, ARG est exécuté après chaque commande simple" [c'est moi qui souligne].

1 votes

J'ai utilisé une combinaison de cette réponse avec certains des éléments spéciaux de la réponse acceptée : trap '[ -n "$COMP_LINE" ] && [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ] && date "+%X";echo -e "\e]0;$BASH_COMMAND\007"' DEBUG . Cela place la commande dans le titre et imprime également l'heure actuelle juste avant chaque commande, mais ne le fait pas lors de l'exécution de la commande. $PROMPT_COMMAND .

1 votes

@CoreDumpError, puisque vous avez refacturé le code, vous devriez nier toutes les conditions : la première devient donc : [ -z "$COMP_LINE" ] .

16voto

dstromberg Points 131

Ce n'est pas une fonction Shell qui est exécutée, mais j'ai contribué à la création d'une fonction $PS0 Chaîne d'invite qui s'affiche avant l'exécution de chaque commande. Détails ici : http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$PS0 est inclus dans bash 4.4, bien qu'il faille un certain temps pour que la plupart des Linux intègrent la 4.4 - vous pouvez cependant compiler la 4.4 vous-même si vous le souhaitez ; dans ce cas, vous devriez probablement le mettre sous /usr/local et l'ajouter à /etc/shells et chsh à celui-ci. Puis déconnectez-vous et reconnectez-vous, peut-être ssh à vous-même@localhost ou su d'abord à vous-même comme un test.

11voto

RCCola Points 101

J'ai récemment dû résoudre ce problème exact pour un de mes projets secondaires. J'ai créé une solution assez robuste et résiliente qui émule les fonctionnalités preexec et precmd de zsh pour bash.

https://github.com/rcaloras/bash-preexec

Il était à l'origine basé sur la solution de Glyph Lefkowitz, mais je l'ai amélioré et mis à jour. Heureux d'aider ou d'ajouter une fonctionnalité si nécessaire.

4voto

Merci pour les conseils ! J'ai fini par utiliser ceci :

#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]"
declare -rx AUDIT_SYSLOG="1" #to use a local syslogd
#
#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
#enable extended pattern matching operators
shopt -s extglob
#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 [ -n "$AUDIT_SYSLOG" ]
        then
            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
        else
            echo $( date +%F_%H:%M:%S ) "$AUDIT_STR $PWD" "${AUDIT_CMD##*( )?(+([0-9])[^0-9])*( )}" >>/var/log/userlog.info
        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="$?"
    if [ -n "$AUDIT_SYSLOG" ]
    then
        logger -p user.info -t "$AUDIT_STR" "#=== bash session ended. ==="
    else
        echo $( date +%F_%H:%M:%S ) "$AUDIT_STR" "#=== bash session ended. ===" >>/var/log/userlog.info
    fi
    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
if [ -n "$AUDIT_SYSLOG" ]
then
    logger -p user.info -t "$AUDIT_STR" "#=== New bash session started. ===" #audit the session openning
else
    echo $( date +%F_%H:%M:%S ) "$AUDIT_STR" "#=== New bash session started. ===" >>/var/log/userlog.info
fi
#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

Profitez-en !

0 votes

J'ai eu un problème avec les commandes bash pipées qui se bloquent... J'ai trouvé un moyen de contourner le problème en utilisant un sous-shell, mais cela fait que 'history -a' ne rafraîchit pas l'historique en dehors du sous-shell... Finalement, la solution a été d'utiliser une fonction qui relit l'historique après l'exécution du sous-shell. Cela fonctionne comme je le voulais. Comme Vaidas l'a écrit sur jablonskis.org/2011/howto-log-bash-history-to-syslog il est plus facile à déployer que Parcheando le bash en C (je l'ai fait aussi dans le passé). mais il y a une certaine baisse de performance en relisant chaque fois le fichier d'historique et en faisant une 'sync' disque...

6 votes

Il serait bon d'alléger ce code ; il est actuellement presque totalement illisible.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X