ทุบตีมีตะขอที่ถูกเรียกใช้ก่อนดำเนินการคำสั่งหรือไม่?


111

ใน bash ฉันสามารถจัดเรียงฟังก์ชั่นที่จะดำเนินการก่อนเรียกใช้คำสั่งได้หรือไม่?

มี$PROMPT_COMMANDซึ่งจะถูกดำเนินการก่อนที่จะแสดงพรอมต์คือหลังจากรันคำสั่ง

Bash's $PROMPT_COMMANDนั้นคล้ายคลึงกับprecmdฟังก์ชั่นของ zsh ; ดังนั้นสิ่งที่ฉันกำลังมองหาเป็นเทียบเท่าทุบตีเพื่อ preexeczsh

แอปพลิเคชันตัวอย่าง: ตั้งค่าชื่อเทอร์มินัลเป็นคำสั่งที่กำลังดำเนินการ เพิ่มโดยอัตโนมัติ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

เคล็ดลับนี้เกิดจากGlyph Lefkowitz ; ขอบคุณbcatสำหรับการค้นหาผู้แต่งดั้งเดิม

แก้ไข แฮ็คของ Glyph รุ่นปรับปรุงสามารถดูได้ที่นี่: https://github.com/rcaloras/bash-preexec


การ"$BASH_COMMAND" = "$PROMPT_COMMAND"เปรียบเทียบไม่ทำงานสำหรับฉันi.imgur.com/blneCdQ.png
laggingreflex

2
ฉันพยายามใช้รหัสนี้ใน cygwin น่าเสียดายที่มันมีเอฟเฟกต์ประสิทธิภาพที่ค่อนข้างแรง - การรันคำสั่ง benchmark อย่างง่ายtime for i in {1..10}; do true; doneใช้เวลาปกติ 0.040 วินาทีและ 1.400 ถึง 1.600 วินาทีหลังจากเปิดใช้งานกับดัก DEBUG มันทำให้เกิดคำสั่งกับดักที่จะดำเนินการสองครั้งต่อห่วง - และ Cygwin ฟอร์กที่จำเป็นสำหรับการดำเนินการ sed ช้าสาหัสที่ประมาณ 0.030 วินาทีฟอร์กเพียงอย่างเดียว (ความแตกต่างระหว่างความเร็วechobuiltin และ/bin/echo) บางทีสิ่งที่ต้องจำไว้
kdb

2
@kdb Cygwin ประสิทธิภาพสำหรับ fork sucks ฉันเข้าใจว่านี่เป็นสิ่งที่หลีกเลี่ยงไม่ได้บน Windows หากคุณต้องการรัน bash code บน Windows ให้ลองลดการฟอร์กกิ้ง
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
น่าสนใจ ... บนเซิร์ฟเวอร์ 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 เนื่องจากคุณได้ refactored รหัสที่คุณควรปฏิเสธเงื่อนไขทั้งหมด: [ -z "$COMP_LINE" ]ครั้งแรกที่หนึ่งจึงกลายเป็น:
cYrus

@ cYrus ขอบคุณ! ฉันไม่รู้การเขียนโปรแกรม bash เกือบจะพอที่จะสังเกตเห็นปัญหานั้นได้
coredumperror

@ LarsH: คุณมีเวอร์ชั่นไหน ฉันมี BASH_VERSION = "4.3.11 (1) - ปล่อย" และมีข้อความว่า "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

ฉันเพิ่งต้องแก้ปัญหาที่แน่นอนนี้สำหรับโครงการด้านข้างของฉัน ฉันสร้างโซลูชันที่แข็งแกร่งและมีความยืดหยุ่นพอที่จะเลียนแบบฟังก์ชั่น preexec และ precmd ของ zsh สำหรับทุบตี

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 ของ piped ที่แฮง ... ฉันพบวิธีแก้ปัญหาโดยใช้ subshell แต่สิ่งนี้ทำให้ 'history -a' ไม่รีเฟรชประวัติภายนอกขอบเขต subshell ... ในที่สุดโซลูชันก็คือการใช้ฟังก์ชั่น ที่อ่านประวัติอีกครั้งหลังจากการดำเนินการของ subshell มันทำงานได้ตามที่ฉันต้องการ ดังที่ Vaidas เขียนไว้ในjablonskis.org/2011/howto-log-bash-history-to-syslogมันง่ายกว่าที่จะนำไปใช้งานได้ง่ายกว่าการแก้ไข bash ใน C (ฉันเคยทำเช่นนั้นในอดีต) แต่มีบางส่วนที่ลดลงในขณะที่ผลการดำเนินงานอีกครั้งอ่านแต่ละครั้งแฟ้มประวัติและการทำดิสก์ 'ซิงค์' ...
Francois Scheurer

5
คุณอาจต้องการตัดรหัสนั้น ปัจจุบันมันอ่านไม่ได้เกือบทั้งหมด
l0b0

3

ฉันเขียนวิธีการบันทึกคำสั่ง / builtins 'bash' ทั้งหมดลงในไฟล์ข้อความหรือเซิร์ฟเวอร์ 'syslog' โดยไม่ต้องใช้ patch หรือเครื่องมือปฏิบัติการพิเศษ

มันง่ายในการปรับใช้เพราะเป็น shellscript ง่าย ๆ ที่ต้องถูกเรียกใช้ครั้งเดียวเมื่อเริ่มต้น 'bash'

ดูวิธีการที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.