bash에는 명령을 실행하기 전에 실행되는 후크가 있습니까?


111

bash에서 명령을 실행하기 직전에 함수를 실행할 수 있습니까?

$PROMPT_COMMAND단지 명령을 실행 한 후, 즉, 프롬프트를 표시하기 전에 실행되는.

Bash $PROMPT_COMMAND는 zsh의 precmd기능 과 유사 합니다. 그래서 내가 찾고있는 것은 zsh 's와 동등한 bash preexec입니다.

응용 프로그램 예 : 터미널 제목을 실행중인 명령으로 설정하십시오. time모든 명령 전에 자동으로 추가하십시오 .


3
bash 버전 4.4에는 PS0동작을 수행 PS1하지만 명령을 읽은 후 실행하기 전에 사용되는 변수가 있습니다. gnu.org/software/bash/manual/bashref.html#Bash-Variables
glenn jackman을

답변:


93

기본적으로는 아니지만 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 에게 감사합니다 .

편집하다. https://github.com/rcaloras/bash-preexec 에서 Glyph의 해킹 업데이트 버전을 확인할 수 있습니다.


"$BASH_COMMAND" = "$PROMPT_COMMAND"비교는 나를 위해 작동하지 않습니다 i.imgur.com/blneCdQ.png
laggingreflex

2
cygwin 에서이 코드를 사용해 보았습니다. 안타깝게도 간단한 벤치 마크 명령을 실행하는 time for i in {1..10}; do true; done데 DEBUG 트랩을 활성화 한 후 보통 0.040 초, 1.400 ~ 1.600 초가 소요됩니다. 이로 인해 트랩 명령이 루프 당 두 번 실행됩니다. Cygwin에서는 sed를 실행하는 데 필요한 포크 속도가 포크 만 약 0.030 초로 엄청나게 느려집니다 ( echo내장과 속도 차이 /bin/echo). 명심해야 할 것.
kdb

2
포크에 대한 @kdb Cygwin 성능이 저하됩니다. 내 이해는 이것이 Windows에서 피할 수 없다는 것입니다. Windows에서 bash 코드를 실행해야하는 경우 분기를 줄이십시오.
Gilles

@DevNull 트랩을 제거하면 매우 쉽게 피할 수 있습니다. 사람들이 할 수 있지만해서는 안되는 일을하는 사람들에게는 기술적 해결책이 없습니다. 부분적인 해결책이 있습니다. 많은 사람에게 많은 액세스 권한을 부여하지 말고, 백업이 최신 상태인지 확인하고, 직접 파일 조작이 아닌 버전 제어를 사용하십시오. 사용자가 쉽게 비활성화 할 수없는 것을 원한다면 혼자서는 전혀 비활성화 할 수 없으므로 셸의 제한은 도움이되지 않습니다. 추가 할 수있는 것처럼 쉽게 제거 할 수 있습니다.
Gilles

1
PROMPT_COMMAND변수에 더 많은 명령이있는 경우 (예 :로 구분 ;) 다음 preexec_invoke_exec과 같이 함수 의 두 번째 줄에서 패턴 일치를 사용해야 [[ "$PROMPT_COMMAND" =~ "$BASH_COMMAND" ]]합니다. BASH_COMMAND각 명령을 개별적으로 나타 내기 때문 입니다.
jirislav

20

에서 trap명령을 사용할 수 있습니다 help trap.

SIGNAL_SPEC가 DEBUG이면 모든 간단한 명령 전에 ARG가 실행됩니다.

예를 들어, 터미널 제목을 동적으로 변경하려면 다음을 사용할 수 있습니다.

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

에서 소스.


1
내 오래된 우분투 서버에서 흥미로운 점 help trap은 "SIGNAL_SPEC가 DEBUG이면 모든 간단한 명령 후에 ARG가 실행된다"고 말합니다 .
LarsH

1
나는이 답변과 허용되는 답변의 특별한 것들을 조합하여 사용했습니다 trap '[ -n "$COMP_LINE" ] && [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ] && date "+%X";echo -e "\e]0;$BASH_COMMAND\007"' DEBUG. 이것은 명령을 제목에 넣고 모든 명령 바로 전에 현재 시간을 인쇄하지만 실행할 때 그렇게하지 않습니다 $PROMPT_COMMAND.
coredumperror

1
@CoreDumpError, 코드를 리팩토링했기 때문에 모든 조건을 무효화해야합니다. 첫 번째 조건은 다음과 같습니다 [ -z "$COMP_LINE" ].
cYrus

@cYrus 감사합니다! 나는 bash 프로그래밍이 그 문제를 알아 차릴만큼 충분히 알지 못한다.
coredumperror

@LarsH : 어떤 버전이 있습니까? BASH_VERSION = "4.3.11 (1) -release"가 있는데 "ARG는 모든 간단한 명령 전에 실행 됩니다."
musiphil

12

실행되는 쉘 함수는 아니지만 $PS0각 명령을 실행하기 전에 표시 되는 프롬프트 문자열을 제공했습니다 . 자세한 내용은 http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$PS0bash4.4에 포함되어 있지만 대부분의 Linux에서 4.4를 포함하는 데 시간이 오래 걸립니다. 원하는 경우 4.4를 직접 빌드 할 수 있습니다. 이 경우, 당신은 아마 아래에 넣어해야 /usr/local,에 추가 /etc/shells하고 chsh그것. 그런 다음, 로그 아웃했다가 다시 로그인하여, 아마도 sshyourself @ localhost su에 테스트 하거나 먼저 자신에게 테스트하십시오.


11

나는 최근에 내 측면 프로젝트 에서이 정확한 문제를 해결해야했습니다. bash에 대한 zsh의 preexec 및 precmd 기능을 에뮬레이트하는 상당히 강력하고 탄력적 인 솔루션을 만들었습니다.

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

원래 Glyph Lefkowitz의 솔루션을 기반으로했지만 개선되어 최신 상태로 유지되었습니다. 필요한 경우 기꺼이 도움을 주거나 기능을 추가하십시오.


3

힌트 주셔서 감사합니다! 나는 이것을 사용하여 끝났다.

#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

즐겨!


파이프 bash 명령에 문제가 발생했습니다 ... 서브 셸을 사용하여 해결 방법을 찾았지만 'history -a'가 서브 셸 범위 밖에서 기록을 새로 고치지 못하게했습니다 ... 마지막으로 솔루션은 함수를 사용하는 것이 었습니다 서브 쉘 실행 후 히스토리를 다시 읽습니다. 내가 원하는대로 작동합니다. Vaidas가 jablonskis.org/2011/howto-log-bash-history-to-syslog에 글을 썼을 때 C에서 bash를 패치하는 것보다 배포하기가 더 쉽습니다 (과거에도 마찬가지였습니다). 하지만 때마다 기록 파일을 다시 읽고 디스크 '동기화'를하는 동안 약간의 성능 저하가 ...
프랑소와 SCHEURER

5
해당 코드를 다듬을 수 있습니다. 현재 거의 완전히 읽을 수 없습니다.
l0b0

3

패치 또는 특수 실행 도구를 사용하지 않고 모든 'bash'명령 / 내장 파일을 텍스트 파일 또는 'syslog'서버에 기록하는 방법을 작성했습니다.

'bash'를 초기화 할 때 한 번 호출해야하는 간단한 셸 스크립트이므로 배포하기가 매우 쉽습니다.

여기 방법을 참조 하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.