在bash中,我可以安排一个函数在运行命令之前执行吗?

$PROMPT_COMMAND,它在显示提示之前即在运行命令之后执行。

Bash的$PROMPT_COMMAND类似于zsh的precmd函数;所以我要寻找的是相当于zsh preexec的bash。示例应用程序:将终端标题设置为正在执行的命令;自动在每个命令前添加time

评论

bash版本4.4具有PS0变量,其作用类似于PS1,但在读取命令之后但在执行之前使用。参见gnu.org/software/bash/manual/bashref.html#Bash-Variables

PS0也可以用于运行zsh的precmd之类的命令-例如PS0 ='$(my_precmd)'。要使提示和命令行的颜色与输出的颜色不同(例如,green = ansi代码32),请在提示中打开绿色PS1 ='\ [\ e [32m \] \ $',然后将其打开在命令执行PS0 ='\ [\ e [0m \]'之前将其关闭。

#1 楼

不是本机的,但是可以使用DEBUG陷阱对其进行破解。此代码设置类似于zsh的preexecprecmd函数。命令行作为单个参数传递给preexec

这是代码的简化版本,用于设置在运行每个命令之前执行的precmd函数。

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


这个技巧归因于Glyph Lefkowitz;感谢bcat查找原始作者。

编辑。可以在以下位置找到Glyph骇客的更新版本:https://github.com/rcaloras/bash-preexec

评论


“ $ BASH_COMMAND” =“ $ PROMPT_COMMAND”比较对我不起作用i.imgur.com/blneCdQ.png

–滞后反射
2014-09-18 0:25



我尝试在cygwin上使用此代码。可悲的是,它在那里对性能的影响非常大-在{1..10}中为i运行一个简单的基准命令时间;做真的完成通常需要0.040秒,并且在激活DEBUG陷阱后需要1.400至1.600秒。它使trap命令在每个循环中执行两次-在Cygwin上,单独执行for sed所需的派生速度非常慢,仅约0.030秒(内置echo和/ bin / echo之间的速度差)。也许要记住一些事情。

– kdb
16年5月24日在12:15

@kdb叉的Cygwin性能糟透了。我的理解是,这在Windows上是不可避免的。如果需要在Windows上运行bash代码,请尝试减少分叉。

–吉尔斯'所以-不再是邪恶的'
16年5月24日在12:20

@DevNull通过删除陷阱可以很容易地避免这种情况。对于允许人们做但不应该做的事情,没有任何技术解决方案。有部分补救措施:不要给尽可能多的人提供访问权限,请确保您的备份是最新的,使用版本控制而不是直接文件操作,…如果您希望用户不轻易禁用某些功能,请让仅凭它根本无法禁用,那么外壳程序中的限制将无济于事:可以像添加它们一样轻松地删除它们。

–吉尔斯'所以-不再是邪恶的'
16年8月24日在6:48

如果在PROMPT_COMMAND变量中有更多命令(例如,用;分隔),则可能需要在preexec_invoke_exec函数的第二行中使用模式匹配,如下所示:[[“” $ PROMPT_COMMAND“ =〜” $ BASH_COMMAND“]] 。这是因为BASH_COMMAND分别表示每个命令。

– Jirislav
19年4月19日在19:23



#2 楼

您可以使用trap命令(来自help trap):


如果SIGNAL_SPEC是DEBUG,则在每个简单命令之前都会执行ARG。


对于例如,要动态更改终端标题,可以使用:

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


从此来源。

评论


有趣的是,...在我的旧版Ubuntu服务器上,帮助陷阱显示“如果SIGNAL_SPEC是DEBUG,则在每个简单命令后都会执行ARG” [强调我的]。

– LarsH
13年4月30日在21:26

我将此答案与接受答案中的一些特殊内容结合使用:trap'[-n“ $ COMP_LINE”] && [“ $ BASH_COMMAND”!=“ $ PROMPT_COMMAND”] &&日期“ +%X”; echo -e“ \ e] 0; $ BASH_COMMAND \ 007”'调试。这会将命令放在标题中,并在每个命令之前立即显示当前时间,但是在执行$ PROMPT_COMMAND时不会这样做。

– coredumperror
2014-09-16 22:50



@CoreDumpError,因为您已经重构了代码,所以您应否定所有条件:因此第一个条件变为:[-z“ $ COMP_LINE”]。

– Cyrus
2014-09-17 8:58

@cYrus谢谢!我不知道几乎没有足够的bash编程注意到这一问题。

– coredumperror
2014年9月18日下午16:46

@LarsH:您有哪个版本?我有BASH_VERSION =“ 4.3.11(1)-release”,它说“ ARG在每个简单命令之前执行。”

–音乐爱好者
2014年11月20日18:21

#3 楼

它不是要执行的Shell函数,但我贡献了一个$PS0提示字符串,该提示字符串在每个命令运行之前显示。此处的详细信息:http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$PS0 4.4包含在bash中,尽管大多数Linux包括4.4都需要一些时间-您如果需要,可以自己构建4.4;在这种情况下,您可能应该将其放在/usr/local下,将其添加到/etc/shellschsh中。然后注销并重新登录,也许首先测试一下自己@localhost,或者首先测试一下自己。

#4 楼

我最近不得不为我的一个副项目解决这个确切的问题。我制作了一个相当健壮和有弹性的解决方案,可以模拟zsh的bash preexec和precmd功能。

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

它最初是基于Glyph Lefkowitz的解决方案,但我对此进行了改进并使其更新。很高兴为您提供帮助或添加功能。

#5 楼

谢谢您的提示!
我最终使用了它:

#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 }')"
declare -rx AUDIT_LOGINPID="$(who -mu | awk '{print }')"
declare -rx AUDIT_USER="$USER" #defined by pam during su/sudo
declare -rx AUDIT_PID="$$"
declare -rx AUDIT_TTY="$(who -mu | awk '{print }')"
declare -rx AUDIT_SSH="$([ -n "$SSH_CONNECTION" ] && echo "$SSH_CONNECTION" | awk '{print ":""->"":"}')"
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


享受吧!

评论


我的管道bash命令挂起有问题...我发现了使用subshel​​l的解决方法,但这导致'history -a'无法刷新subshel​​l范围之外的历史记录...最后,解决方案是使用一个函数在执行子Shell之后重新读取历史记录。它可以按我的意愿工作。正如Vaidas在jablonskis.org/2011/howto-log-bash-history-to-syslog上所写的那样,与在C中修补bash相比,部署起来更容易(我过去也这样做)。但是每次重新读取历史文件并进行磁盘“同步”时,性能都会有所下降...

– Francois scheurer
2012年2月7日在22:18



您可能要修剪该代码;目前,它几乎完全不可读。

–l0b0
2012年3月22日上午11:27

#6 楼

我编写了一种方法,无需使用修补程序或特殊的可执行工具即可将所有“ bash”命令/内建命令记录到文本文件或“ syslog”服务器中。

由于部署起来非常容易,因为这是一个简单的shellscript,需要在'bash'初始化时调用一次。

请参见此处的方法。