Чи має bash гачок, який запускається перед виконанням команди?


111

Чи можу я організувати виконання функції безпосередньо перед запуском команди?

Є $PROMPT_COMMAND, що виконується перед показом запиту, тобто відразу після запуску команди.

Баш $PROMPT_COMMANDаналогічний precmdфункції zsh ; тож те, що я шукаю, - це баш-еквівалент zsh's preexec.

Приклади програм: встановіть заголовок терміналу для команди, що виконується; автоматично додавати timeперед кожною командою.


3
версія bash 4.4 має PS0змінну, яка діє на зразок, PS1але використовується після читання команди, але перед її виконанням. Дивіться gnu.org/software/bash/manual/bashref.html#Bash-Variables
glenn jackman

Відповіді:


93

Не рідно, але його можна зламати за допомогою 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"Порівняння не працює для мене i.imgur.com/blneCdQ.png
laggingreflex

2
Я спробував використовувати цей код на cygwin. На жаль, він має досить інтенсивні ефекти на продуктивність - виконання звичайної команди-орієнтира time for i in {1..10}; do true; doneзаймає нормально 0,040 секунд і після активації пастки DEBUG - від 1.400 до 1.600 секунд. Це призводить до того, що команда пастки виконується двічі за цикл - а на Cygwin розгортання, необхідне для виконання sed, непомітно повільне, приблизно приблизно 0,030 секунд для розгортання в поодинці (різниця швидкостей між echoвбудованим та /bin/echo). Щось, про що можна пам’ятати.
kdb

2
Продуктивність @kdb Cygwin для вилки відсмоктує. Я розумію, що це неминуче в Windows. Якщо вам потрібно запустити bash-код у Windows, спробуйте скоротити форкінг.
Жиль

@DevNull Це можна дуже легко обійти, видаливши пастку. Немає технічного рішення для людей, які роблять те, що їм дозволено робити, але не повинні робити. Існують часткові засоби захисту: не надайте стільки людей доступного доступу, переконайтесь, що ваші резервні копії оновлені, використовуйте контроль версій, а не прямі маніпуляції файлами… Якщо ви хочете щось, що користувачі не можуть легко відключити, нехай поодинці не можна взагалі відключити, тоді обмеження в оболонці вам не допоможуть: їх можна зняти так само легко, як і їх можна додати.
Жиль

1
Якщо у вас є кілька команд в PROMPT_COMMANDзмінної (наприклад , обмежена ;), вам може знадобитися використовувати зіставлення з зразком у другому рядку preexec_invoke_execфункції, так само , як це: [[ "$PROMPT_COMMAND" =~ "$BASH_COMMAND" ]]. Це тому, що BASH_COMMANDпредставляє кожну з команд окремо.
Джиріслав

20

Ви можете використовувати trapкоманду (від help trap):

Якщо SIGNAL_SPEC DEBUG, ARG виконується перед кожною простою командою.

Наприклад, щоб динамічно змінити назву терміналу, ви можете використовувати:

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

З цього джерела.


1
Цікаво ... на моєму старому сервері Ubuntu 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/

$PS0включено в bash4.4, хоча для більшості Linuxes буде потрібно певний час, а саме 4.4 - ви можете створити 4.4 самостійно, якщо хочете; у такому випадку ви, ймовірно, повинні помістити його /usr/local, додати до нього /etc/shellsта chshдо нього. Потім вийдіть із системи та знову ввійдіть, можливо, sshспочатку до себе @ localhost або suспочатку до себе як тест.


11

Нещодавно мені довелося вирішити цю точну проблему для мого побічного проекту. Я зробив досить надійне і еластичне рішення, яке емулює функцію preshec zsh і precmd для bash.

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

Спочатку він базувався на рішенні Гліфа Лефковіца, але я вдосконалив його і довів його до цього часу. Раді допомогти або додати функцію, якщо потрібно.


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 bash, які зависають ... Я знайшов вирішення за допомогою підрозділу, але це призвело до того, що "history -a" не оновлював історію за межами області підкашлю ... Нарешті рішенням було використання функції які перечитують історію після виконання додаткової оболонки. Це працює так, як я хотів. Як писав Вайдас на jablonskis.org/2011/howto-log-bash-history-to-syslog , його легше розгорнути, ніж виправити баш в C (я це робив і в минулому). але спостерігається деяке падіння продуктивності під час перечитування кожного разу, коли файл історії та синхронізація диска ...
Франсуа Шьюрер

5
Ви можете обрізати цей код; в даний час це майже повністю не читається.
l0b0

3

Я написав метод для реєстрації всіх команд / вбудованих файлів 'bash' у текстовий файл або на сервер 'syslog', не використовуючи патч або спеціальний виконуваний інструмент.

Розгортати це дуже просто, оскільки це простий оболонка, яку потрібно викликати один раз при ініціалізації "bash".

Дивіться метод тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.