Questão O bash tem um gancho que é executado antes de executar um comando?


No bash, posso organizar uma função para ser executada antes de executar um comando?

Há sim $PROMPT_COMMAND, que é executado antes de mostrar um prompt, ou seja, logo após executar um comando.

Bash's $PROMPT_COMMAND é análogo ao de zsh precmd função; então o que eu estou procurando é um equivalente bash para zsh preexec.

Exemplo de aplicações: defina seu título de terminal para o comando que está sendo executado; adicionar automaticamente time antes de cada comando.


99


origem


bash versão 4.4 tem um PS0 variável que age como PS1 mas é usado depois de ler o comando, mas antes de executá-lo. Vejo gnu.org/software/bash/manual/bashref.html#Bash-Variables - glenn jackman


Respostas:


Não nativamente, mas pode ser hackeado usando o DEBUG armadilha. Este código configura preexec e precmd funções semelhantes a zsh. A linha de comando é passada como um único argumento para preexec.

Aqui está uma versão simplificada do código para configurar precmd função que é executada antes de executar cada comando.

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

Este truque é devido a Glyph Lefkowitz; graças a bcat para localizar o autor original.

Editar. Uma versão atualizada do hack de Glyph pode ser encontrada aqui: https://github.com/rcaloras/bash-preexec


81



o "$BASH_COMMAND" = "$PROMPT_COMMAND" comparação não está funcionando para mim i.imgur.com/blneCdQ.png - laggingreflex
Eu tentei usar este código no cygwin. Infelizmente, tem efeitos de performance bastante intensos lá - executando um simples comando benchmark time for i in {1..10}; do true; doneleva 0,040 segundos normalmente e 1.400 a 1.600 segundos depois de ativar o trap DEBUG. Isso faz com que o comando trap seja executado duas vezes por loop - e no Cygwin o forking necessário para executar o sed é proibitivamente lento em aproximadamente 0.030 segundos para o forking sozinho (diferença de velocidade entre echo construído e /bin/echo). Algo para ter em mente talvez. - kdb
O desempenho do @kdb Cygwin para o garfo é uma merda. Meu entendimento é que isso é inevitável no Windows. Se você precisar executar o código bash no Windows, tente reduzir o número de bifurcações. - Gilles
@DevNull Isso pode ser facilmente contornado pela remoção da armadilha. Não há solução técnica para as pessoas que estão fazendo o que podem, mas não devem fazer. Existem soluções parciais: não dê acesso a tantas pessoas, certifique-se de que seus backups estejam atualizados, use o controle de versão em vez da manipulação direta de arquivos,… Se você quer algo que os usuários não podem desativar facilmente, deixe sozinho não pode desabilitar de jeito nenhum, então as restrições no shell não irão ajudá-lo: elas podem ser removidas tão facilmente quanto podem ser adicionadas. - Gilles
@Glyph Isso pode ser modificado para não executar o comando de usuário real se ele não atender a determinados critérios (por exemplo: bloquear um comando de execução e imprimir um aviso se ele contiver linguagem indevida de um arquivo de dicionário)? - DevNull


Você pode usar o trap comando (de help trap):

Se um SIGNAL_SPEC for DEBUG, o ARG será executado antes de cada comando simples.

Por exemplo, para alterar o título do terminal dinamicamente, você pode usar:

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

A partir de esta fonte.


16



Interessante ... no meu antigo servidor Ubuntu, help trap diz "Se um SIGNAL_SPEC é DEBUG, o ARG é executado depois de todo comando simples "[ênfase minha]. - LarsH
Eu usei uma combinação dessa resposta com algumas das coisas especiais na resposta aceita: trap '[ -n "$COMP_LINE" ] && [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ] && date "+%X";echo -e "\e]0;$BASH_COMMAND\007"' DEBUG. Isso coloca o comando no título e também imprime a hora atual antes de cada comando, mas não o faz ao executar $PROMPT_COMMAND. - CoreDumpError
@CoreDumpError, desde que você refatore o código, você deve negar todas as condições: a primeira se torna: [ -z "$COMP_LINE" ]. - cYrus
@cYrus Obrigado! Eu não sei quase bash programação suficiente para ter notado que o problema. - CoreDumpError
@LarsH: Qual versão você tem? Eu tenho BASH_VERSION = "4.3.11 (1) -release" e diz "ARG é executado antes cada comando simples ". - musiphil


Não é uma função de shell executada, mas eu contribuí com uma string de prompt $ PS0 que é exibida antes de cada comando ser executado. Detalhes aqui: http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$ PS0 está incluído no bash 4.4, embora demore um pouco para a maioria dos Linuxes incluir 4.4 - você pode construir 4.4 se quiser; Nesse caso, você provavelmente deve colocá-lo em / usr / local, adicioná-lo ao / etc / shells e chsh para ele. Então saia e volte, talvez ssh para si mesmo @ localhost ou su'ing para si mesmo primeiro como um teste.


9





Recentemente tive que resolver esse problema exato para um projeto paralelo meu. Fiz uma solução bastante robusta e resiliente que emula a funcionalidade preexec e precmd do zsh para o bash.

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

Ele foi originalmente baseado na solução de Glyph Lefkowitz, mas eu melhorei e atualizei. Feliz em ajudar ou adicionar um recurso, se necessário.


8





Eu escrevi um método para registrar todos os comandos / builtins 'bash' em um arquivo de texto ou um servidor 'syslog' sem usar um patch ou uma ferramenta executável especial.

É muito fácil de implantar, pois é um simples shellscript que precisa ser chamado uma vez na inicialização do 'bash'.

Veja o método Aqui.


3





Obrigado pelas dicas! Acabei usando isso:

#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

Apreciar!


2



Eu tive um problema com comandos bash canalizados que trava ... Eu encontrei uma solução usando um subshell, mas isso causou o 'histórico -a' para não atualizar o histórico fora do escopo subshell ... Finalmente, a solução era usar uma função que rele a história após a execução da sub-camada. Funciona como eu queria. Como Vaidas escreveu em jablonskis.org/2011/howto-log-bash-history-to-syslog, é mais fácil implantar do que corrigir o bash em C (eu fiz isso também no passado). mas há alguma queda de desempenho ao reler cada vez que o arquivo de histórico e fazendo um disco 'sync' ... - francois scheurer
Você pode querer cortar esse código; atualmente é quase completamente ilegível. - l0b0