Есть ли у bash ловушка, которая запускается перед выполнением команды?

38430
Gilles

Можно ли в bash организовать выполнение функции непосредственно перед выполнением команды?

Существует $PROMPT_COMMAND, которая выполняется перед отображением приглашения, т. Е. Сразу после запуска команды.

Bash $PROMPT_COMMANDаналогичен precmdфункции Zsh ; так что я ищу bash, эквивалентный Zsh preexec.

Примеры приложений: установите заголовок терминала для выполняемой команды; автоматически добавлять timeперед каждой командой.

99
bash версии 4.4 имеет переменную `PS0`, которая действует как` PS1`, но используется после чтения команды, но перед ее выполнением. См. Https://www.gnu.org/software/bash/manual/bashref.html#Bash-Variables. glenn jackman 6 лет назад 1

6 ответов на вопрос

84
Gilles

Не изначально, но его можно взломать с помощью DEBUGловушки. Этот код настраивается preexecи precmdфункционирует аналогично zsh. Командная строка передается как один аргумент 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 

Этот трюк из-за Глифа Лефковица ; спасибо bcat за поиск оригинального автора.

Редактировать. Обновленную версию взлома Glyph можно найти здесь: https://github.com/rcaloras/bash-preexec

Сравнение `" $ BASH_COMMAND "=" $ PROMPT_COMMAND "` для меня не работает http://i.imgur.com/blneCdQ.png laggingreflex 9 лет назад 0
Я пытался использовать этот код на Cygwin. К сожалению, у него довольно интенсивный эффект производительности - выполнение простой команды теста `time for i в ; делать правду; done` обычно занимает 0,040 секунды и от 1,400 до 1,600 секунд после активации ловушки DEBUG. Это приводит к тому, что команда trap выполняется дважды за цикл - и на Cygwin разветвление, необходимое для выполнения sed, непомерно медленно - примерно 0,030 секунды для разветвления в одиночку (разность скоростей между встроенными `echo` и` / bin / echo`). Что-то, чтобы иметь в виду, может быть. kdb 7 лет назад 1
@kdb Cygwin производительность для вилки отстой. Насколько я понимаю, это неизбежно в Windows. Если вам нужно запустить bash-код в Windows, попробуйте сократить число разветвлений. Gilles 7 лет назад 0
@DevNull Это может быть очень легко обойти, удалив ловушку. Не существует технического решения для людей, которые делают то, что им разрешено, но не должны делать. Есть частичные меры: не предоставляйте столько людей, сколько доступа, убедитесь, что ваши резервные копии обновлены, используйте контроль версий, а не прямое манипулирование файлами ... Если вы хотите что-то, что пользователи не могут легко отключить, позвольте одни не могут вообще отключить, тогда ограничения в оболочке вам не помогут: их можно удалить так же легко, как и добавить. Gilles 7 лет назад 0
@Glyph Может ли это быть изменено, чтобы не запускать действительную пользовательскую команду, если она не соответствует определенным критериям (например: заблокировать выполнение команды и вывести предупреждение, если она содержит нецензурную лексику из файла словаря)? DevNull 7 лет назад 0
16
cYrus

Вы можете использовать trapкоманду (из help trap):

Если SIGNAL_SPEC - DEBUG, ARG выполняется перед каждой простой командой.

Например, для динамического изменения названия терминала вы можете использовать:

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

Из этого источника.

Интересно ... на моем старом сервере Ubuntu `help trap` говорит:« Если SIGNAL_SPEC - DEBUG, ARG выполняется ** после ** каждой простой команды »[выделено мной]. LarsH 11 лет назад 1
Я использовал комбинацию этого ответа с некоторыми специальными вещами в принятом ответе: `trap '[-n" $ COMP_LINE "] && [" $ BASH_COMMAND "! =" $ PROMPT_COMMAND "] && date" +% X "; echo -e "\ e] 0; $ BASH_COMMAND \ 007" 'DEBUG`. Это помещает команду в заголовок, а также печатает текущее время непосредственно перед каждой командой, но не делает этого при выполнении `$ PROMPT_COMMAND`. CoreDumpError 9 лет назад 1
@CoreDumpError, так как вы реорганизовали код, вы должны отменить все условия: первое, следовательно, становится: `[-z" $ COMP_LINE "]`. cYrus 9 лет назад 1
@cYrus Спасибо! Я не знаю почти достаточно программирования на bash, чтобы заметить эту проблему. CoreDumpError 9 лет назад 0
@LarsH: Какая у вас версия? У меня есть BASH_VERSION = "4.3.11 (1) -release" и там написано "ARG выполняется * перед * каждой простой командой". musiphil 9 лет назад 0
@musiphil: я не помню. Это был сервер, который, вероятно, больше не существует, в организации, на которую я больше не работаю. LarsH 9 лет назад 0
10
RCCola

I recently had to solve this exact problem for a side project of mine. I made a fairly robust and resilient solution that emulates zsh's preexec and precmd functionality for bash.

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

It was originally based off Glyph Lefkowitz's solution, but I've improved on it and brought it up to date. Happy to help or add a feature if needed.

9
dstromberg

Это не функция оболочки, которая выполняется, но я добавил строку приглашения $ PS0, которая отображается перед выполнением каждой команды. Подробности здесь: http://stromberg.dnsalias.org/~strombrg/PS0-prompt/

$ PS0 включен в bash 4.4, хотя большинству Linux потребуется некоторое время, чтобы включить 4.4 - вы можете собрать 4.4 самостоятельно, если хотите; в этом случае вы, вероятно, должны поместить его в / usr / local, добавить в / etc / shells и chsh в него. Затем выйдите из системы и вернитесь обратно, возможно ssh'ing для себя @ localhost или сначала для себя в качестве теста.

3
francois scheurer

Спасибо за подсказки! Я закончил тем, что использовал это:

#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="\[$$\]\u@\h:\[$$\]\w\\$\[$$\] "  #'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 '')" declare -rx AUDIT_LOGINPID="$(who -mu | awk '')" declare -rx AUDIT_USER="$USER" #defined by pam during su/sudo declare -rx AUDIT_PID="$$" declare -rx AUDIT_TTY="$(who -mu | awk '')" declare -rx AUDIT_SSH="$([ -n "$SSH_CONNECTION" ] && echo "$SSH_CONNECTION" | awk '')" 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===$$\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 "$$" # (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 "$$" 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" "$" then echo error "$AUDIT_STR $PWD" "$" fi else echo $( date +%F_%H:%M:%S ) "$AUDIT_STR $PWD" "$" >>/var/log/userlog.info fi fi #echo "===cmd:$BASH_COMMAND/subshell:$BASH_SUBSHELL/fc:$(fc -l -1)/history:$(history 1)/histline:$===" #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 ... Я нашел обходной путь с использованием subshell, но это привело к тому, что «history -a» не обновляет историю за пределами области действия subshell ... Наконец, решение заключалось в использовании функции что перечитать историю после выполнения подоболочки. Это работает, как я хотел. Как писал Вайдас на http://jablonskis.org/2011/howto-log-bash-history-to-syslog/, его легче развернуть, чем исправление bash в C (я тоже так делал в прошлом). но происходит некоторое снижение производительности при повторном чтении файла истории и выполнении синхронизации диска ... francois scheurer 12 лет назад 0
Возможно, вы захотите урезать этот код; в настоящее время это почти полностью нечитаемо. l0b0 12 лет назад 5
3
Francois Scheurer

Я написал метод для записи всех команд / встроенных команд bash в текстовый файл или сервер 'syslog' без использования патча или специального исполняемого инструмента.

Его очень легко развернуть, так как это простой шелл-скрипт, который нужно вызывать один раз при инициализации 'bash'.

Смотрите метод здесь .

Похожие вопросы