bash_history:註解掉危險指令:`#`

bash_history:註解掉危險指令:`#`

為了防止在 bash 歷史記錄中記錄「危險」命令,我在文件中新增了以下行.bashrc

HISTIGNORE='rm *:mv *:cp *:cat*>*:pv*>*'

這很有效,但有一個副作用:我無法看到機器上執行的命令的完整歷史記錄。假設我有幾台機器用於實驗,我希望能夠看到執行的所有命令。我會使用 bash 內部history來顯示執行的命令,也許還可以使用 grep 顯示今天的日期:

history | grep Sep-28

我想要做的是也記錄“危險”命令,但#在行的開頭放置一個,這樣如果我碰巧錯誤地執行了歷史記錄中的命令,就不會造成任何損害。

我不知道這是否可能。

更新和澄清:

這對我來說是一個問題的主要原因是我通常從多個終端連接到我的機器,並且在一個終端上執行的任何命令都會立即讀入其他終端的歷史記錄中。這是透過以下方式實現的

PROMPT_COMMAND="history -a; history -c; history -r"

假設我有兩個終端打開。在其中一個進程中,我正在cat /dev/foo > file.out運行一些進程。在第二步驟中,我使用 檢查進度ls -lAhF。我ls透過按UpENTER(即歷史記錄中的最後一個命令)不斷重複。一旦第一個命令完成,歷史記錄中的最後一個命令就不再是ls,而是cat /dev/foo > file.out。如果我不小心,我會再次啟動cat並覆蓋file.out。

我想要實現的是 cat 指令前面帶有 a #,這樣它就不會被執行。然而,我仍然會在歷史中看到它,並且可以透過取消註釋來重複使用它(如果它是一個長命令)。

答案1

我不會準確回答您的問題,但也許會為您的問題提供替代解決方案。

如果我理解正確的話,您會擔心輸入時可能會犯的錯誤,例如,!rm如果rm歷史記錄中的上一個命令刪除了您想要保留的內容。

在這種情況下,一個好的巴什選項是histverify.如果shopt -s histverify您使用 bang 呼叫命令!,它不會立即執行,而是載入到 readline 中,以便您可以決定執行或不執行它,這也使您可以編輯它。

試試一下:

  • 沒有histverify

    $ touch some_foo
    $ rm some_foo
    $ touch some_foo
    $ !rm
    rm some_foo
    $ # oooops in fact I'd've like to keep it this time
    
  • histverify

    $ shopt -s histverify
    $ touch some_foo
    $ rm some_foo
    $ touch some_foo
    $ !rm
    $ rm some_foo <cursor here>
    

    在這種情況下,您的遊標將位於該行的末尾,準備好再次啟動它(或不啟動它)或編輯它。

如果您喜歡此選項,請將其放入您的.bashrc

shopt -s histverify

答案2

你可以這樣做:

fixhist() {
   local cmd histnum
   cmd=$(HISTTIMEFORMAT=/ history 1)
   histnum=$((${cmd%%[*/]*}))
   cmd=${cmd#*/} # remove the histnum
   case $cmd in
     (rm\ *|mv\ *|...)
       history -d "$histnum" # delete
       history -s "#$cmd"    # add back with a #
   esac
}
PROMPT_COMMAND=fixhist

這個想法是,在每個提示之前,我們檢查最後一個歷史記錄條目 ( history 1) 以及它是否是其中之一危險的,我們刪除它 ( history -d) 並用#with將其加回來history -s

(顯然,您需要刪除您的HISTIGNORE設定)。

但它的一個不想要的副作用是它改變了歷史時間其中rmmv...指令。

為了解決這個問題,替代方案可能是:

fixhist() {
   local cmd time histnum
   cmd=$(HISTTIMEFORMAT='<%s>' history 1)
   histnum=$((${cmd%%[<*]*}))
   time=${cmd%%>*}
   time=${time#*<}
   cmd=${cmd#*>}
   case $cmd in
     (rm\ *|mv\ *|...)
       history -d "$histnum" # delete
       HISTFILE=/dev/stdin history -r <<EOF
#$time
#$cmd
EOF
   esac
}
PROMPT_COMMAND=fixhist

這次,我們記錄最後一個歷史記錄的時間,並添加回歷史行,我們使用history -r包含時間戳的臨時文件(此處文檔)。

您希望fixhist在您的 之前執行history -a; history -c; history -r。不幸的是,目前版本bash有一個錯誤,history -a無法儲存我們新增的額外行。解決方法是這樣寫:

fixhist() {
   local cmd time histnum
   cmd=$(HISTTIMEFORMAT='<%s>' history 1)
   histnum=$((${cmd%%[<*]*}))
   time=${cmd%%>*}
   time=${time#*<}
   cmd=${cmd#*>}
   case $cmd in
     (rm\ *|mv\ *|...)
       history -d "$histnum" # delete
       history -a
       [ -f "$HISTFILE" ] && printf '#%s\n' "$time" "$cmd" >> "$HISTFILE";;
     (*)
       history -a
   esac
   history -c
   history -r
}
PROMPT_COMMAND=fixhist

也就是我們自己將註解掉的指令追加到 HISTFILE 中,而不是讓其history -a去做。

答案3

已接受 Stéphane 的回答絕對是驚人的。下面是我採用的它,它的模式匹配有點不同。

使用ifstatements 而不是case允許我使用正規表示式匹配 ( [[ "$cmd" =~ <REGEX> ]]),下面的程式碼按以下邏輯工作:

  • 在以下情況下不要新增到歷史記錄:
    • 命令列以 2 個或更多連續空格開始或結束
      範例:
      • <spc><spc>history
  • 新增以下情況下註解的命令:
    • rmmv帶有 cli 開關的命令
    • 命令列以:;或 單一空格開頭或以;:
      範例結尾:
    • rm -R /tmp/somedir

    • echo "doing something dangerous I don't want to trip by accident";:

      這也有魚子醬,我對此很滿意,例如:

    • echo ";rm -something"

  • 否則新增到歷史記錄
    範例:
    • rm somefile
    • echo ";rm -something"

程式碼

_history_hook() {
# The code below is from https://unix.stackexchange.com/a/110056/138012
# history builtiun options used:  https://www.gnu.org/software/bash/manual/html_node/Bash-History-Builtins.html
# history -a : Append new (mem) lines to history (file)
# history -d : Delete range (mem)
# history -c : Clear history list (mem) ++
# history -r : Read (file) append to (mem) ++
# ++ needed to circumvent a bug issue.
   local cmd time histnum
   cmd=$(HISTTIMEFORMAT='<%s>' history 1)
   histnum=$((${cmd%%[<*]*}))
   time="${cmd%%>*}"
   time=${time#*<}
   cmd="${cmd#*>}"
   # different behaviors for different patterns
   if [[ "$cmd" =~ ^\ \ +.*|\ +\ $ ]]; then
   # delete only (two spaces - leading or trailing)
       history -d "$histnum" # delete
   elif [[ "$cmd" =~ ((^|;[[:space:]]*)(rm|mv)\ +-)|^\ |^:\;|\;:$ ]]; then
   # rewrite history as a 'safe version'
       history -d "$histnum" # delete
       history -a
       [ -f "$HISTFILE" ] && printf '# %s\n' "$time" "$cmd" >> "$HISTFILE"
   else
   # append 'as usual'
       history -a
   fi
   # rewriting history file
   history -c
   history -r
}

export HISTCONTROL=erasedups # This does not seem to work right now - need to fix this
export PROMPT_COMMAND="$PROMPT_COMMAND; _history_hook"

相關內容