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"

相关内容