Hat bash einen Hook, der ausgeführt wird, bevor ein Befehl ausgeführt wird?


111

Kann ich in bash veranlassen, dass eine Funktion ausgeführt wird, bevor ein Befehl ausgeführt wird?

Es gibt $PROMPT_COMMANDeinen Befehl, der ausgeführt wird, bevor eine Eingabeaufforderung angezeigt wird, dh kurz nachdem ein Befehl ausgeführt wurde.

Bashs $PROMPT_COMMANDist analog zu zshs precmdFunktion; Also, was ich suche, ist eine Bash-Entsprechung zu zshs preexec.

Beispielanwendungen: Setzen Sie Ihren Terminaltitel auf den Befehl, der ausgeführt wird. automatisch timevor jedem Befehl hinzufügen .


3
Die bash-Version 4.4 enthält eine PS0Variable, die sich wie eine Variable verhält, PS1aber nach dem Lesen des Befehls, aber vor dessen Ausführung verwendet wird. Siehe gnu.org/software/bash/manual/bashref.html#Bash-Variables
Glenn Jackman

Antworten:


93

Nicht von Haus aus, kann aber mit der DEBUGFalle gehackt werden . Dieser Code richtet ein preexecund precmdfunktioniert ähnlich wie zsh. Die Befehlszeile wird als einzelnes Argument an übergeben preexec.

Hier ist eine vereinfachte Version des Codes zum Einrichten einer precmdFunktion, die vor dem Ausführen der einzelnen Befehle ausgeführt wird.

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

Dieser Trick geht auf Glyph Lefkowitz zurück . danke an bcat für das lokalisieren des ursprünglichen autors .

Bearbeiten. Eine aktualisierte Version von Glyphs Hack finden Sie hier: https://github.com/rcaloras/bash-preexec


Der "$BASH_COMMAND" = "$PROMPT_COMMAND"Vergleich funktioniert bei mir nicht i.imgur.com/blneCdQ.png
laggingreflex

2
Ich habe versucht, diesen Code auf Cygwin zu verwenden. Leider hat es dort ziemlich starke Leistungseffekte - das Ausführen eines einfachen Benchmark-Befehls time for i in {1..10}; do true; donedauert normalerweise 0,040 Sekunden und 1.400 bis 1.600 Sekunden, nachdem die DEBUG-Falle aktiviert wurde. Dadurch wird der Trap-Befehl zweimal pro Schleife ausgeführt - und bei Cygwin ist das zum Ausführen von sed erforderliche Gabeln mit ungefähr 0,030 Sekunden für das alleinige Gabeln (Geschwindigkeitsdifferenz zwischen echoeingebautem und /bin/echo) unerschwinglich langsam . Vielleicht etwas, an das man denken sollte.
kdb

2
@kdb Cygwin Leistung für Gabel saugt. Ich verstehe, dass dies unter Windows unvermeidlich ist. Wenn Sie Bash-Code unter Windows ausführen müssen, versuchen Sie, das Gabeln zu reduzieren.
Gilles

@DevNull Dies kann sehr leicht umgangen werden, indem die Falle entfernt wird. Es gibt keine technische Lösung für Menschen, die das tun, was sie dürfen, aber nicht tun sollten. Es gibt teilweise Abhilfemaßnahmen: Geben Sie nicht so vielen Personen Zugriff, stellen Sie sicher, dass Ihre Backups auf dem neuesten Stand sind, verwenden Sie die Versionskontrolle und nicht die direkte Manipulation von Dateien. Wenn Sie etwas möchten, das Benutzer nicht einfach deaktivieren können, lassen Sie Alleine kann man das überhaupt nicht deaktivieren, dann helfen dir Einschränkungen in der Shell nicht weiter: Sie können genauso einfach entfernt werden, wie sie hinzugefügt werden können.
Gilles

1
Wenn Sie mehrere Befehle in einem haben PROMPT_COMMANDVariable (zB begrenzt durch ;), müssen Sie möglicherweise Musterabgleich in der zweiten Zeile der verwenden preexec_invoke_execFunktion, wie folgt aus : [[ "$PROMPT_COMMAND" =~ "$BASH_COMMAND" ]]. Dies liegt daran BASH_COMMAND, dass jeder der Befehle separat dargestellt wird.
Jirislav

20

Sie können den trapBefehl (von help trap) verwenden:

Wenn ein SIGNAL_SPEC DEBUG ist, wird ARG vor jedem einfachen Befehl ausgeführt.

Um beispielsweise den Terminaltitel dynamisch zu ändern, können Sie Folgendes verwenden:

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

Aus dieser Quelle.


1
Interessant ... auf meinem alten Ubuntu-Server help trapheißt es "Wenn ein SIGNAL_SPEC DEBUG ist, wird ARG nach jedem einfachen Befehl ausgeführt".
LarsH

1
Ich benutzte eine Kombination dieser Antwort mit einigen der speziellen Sachen in der akzeptierte Antwort: trap '[ -n "$COMP_LINE" ] && [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ] && date "+%X";echo -e "\e]0;$BASH_COMMAND\007"' DEBUG. Dadurch wird der Befehl in den Titel eingefügt und die aktuelle Zeit direkt vor jedem Befehl gedruckt, dies geschieht jedoch nicht bei der Ausführung $PROMPT_COMMAND.
Coredumperror

1
@CoreDumpError, da Sie den Code Refactoring haben , sollten Sie alle Bedingungen negieren: das erste daher wird: [ -z "$COMP_LINE" ].
CYRUS

@ Cyrus Danke! Ich kenne fast nicht genug Bash-Programmierung, um dieses Problem bemerkt zu haben.
Coredumperror

@ LarsH: Welche Version hast du? Ich habe BASH_VERSION = "4.3.11 (1) -release" und es heißt "ARG wird vor jedem einfachen Befehl ausgeführt."
Musiphil

12

Es ist keine Shell-Funktion, die ausgeführt wird, aber ich habe eine $PS0Eingabeaufforderung eingegeben, die angezeigt wird, bevor ein Befehl ausgeführt wird. Details hier: http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$PS0ist in bash4.4 enthalten, obwohl es eine Weile dauern wird, bis die meisten Linux-Versionen 4.4 enthalten - Sie können jedoch auch selbst 4.4 erstellen. in diesem Fall sollten Sie es wahrscheinlich unterlegen /usr/local, hinzufügen /etc/shellsund hinzufügen chsh. Dann loggen Sie sich aus und wieder ein, vielleicht als Test sshfür sich selbst @ localhost oder sufür sich selbst.


11

Ich musste vor kurzem genau dieses Problem für ein Nebenprojekt von mir lösen. Ich habe eine ziemlich robuste und ausfallsichere Lösung erstellt, die die Preexec- und Precmd-Funktionalität von zsh für bash emuliert.

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

Es basierte ursprünglich auf der Lösung von Glyph Lefkowitz, aber ich habe es verbessert und auf den neuesten Stand gebracht. Gerne helfen wir Ihnen weiter oder fügen bei Bedarf eine Funktion hinzu.


3

Danke für die Hinweise! Am Ende habe ich Folgendes verwendet:

#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

Genießen!


Ich hatte ein Problem mit Pipe-bash-Befehlen, die hängengeblieben sind ... Ich habe eine Problemumgehung mit einer Subshell gefunden, aber dies hat dazu geführt, dass 'history -a' den Verlauf nicht außerhalb des Subshell-Bereichs aktualisiert. Schließlich bestand die Lösung darin, eine Funktion zu verwenden das liest den Verlauf nach der Subshell-Ausführung erneut. Es funktioniert wie ich wollte. Wie Vaidas auf jablonskis.org/2011/howto-log-bash-history-to-syslog schrieb , ist die Bereitstellung einfacher als das Patchen der Bash in C (das habe ich auch in der Vergangenheit getan). Aber es gibt einige Leistungseinbußen beim erneuten Lesen der Verlaufsdatei und beim Ausführen einer Datenträgersynchronisierung ...
François Scheurer

5
Vielleicht möchten Sie diesen Code zuschneiden. Derzeit ist es fast völlig unlesbar.
l0b0

3

Ich habe eine Methode geschrieben, um alle 'bash'-Befehle / eingebauten Befehle in eine Textdatei oder einen' syslog'-Server zu schreiben, ohne einen Patch oder ein spezielles ausführbares Tool zu verwenden.

Die Bereitstellung ist sehr einfach, da es sich um ein einfaches Shellscript handelt, das bei der Initialisierung der 'Bash' einmal aufgerufen werden muss.

Siehe die Methode hier .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.