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нажимая Upи ENTER(то есть последнюю команду из истории). Как только первая команда завершается, последней командой из истории становится уже не ls, а cat /dev/foo > file.out. Если я не буду осторожен, я снова запущу cat и перезапишу file.out.

Я хотел бы добиться того, чтобы команде cat предшествовал #, чтобы она не выполнялась. Однако я бы все равно видел ее в истории и мог бы повторно использовать ее (если это длинная команда), раскомментировав ее.

решение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) и добавляем обратно с #помощью history -s.

(очевидно, вам нужно удалить вашу HISTIGNOREнастройку).

Нежелательным побочным эффектом этого является то, что это изменяетвремя историииз них rm, mv... команды.

Чтобы исправить это, можно сделать следующее:

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временный файл (документ here), который включает временную метку.

Вы бы хотели, чтобы 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

Theпринятый ответ от Стефанаабсолютно потрясающе. Ниже представлен мой вариант, который немного отличается от сопоставления с образцом.

Использование ifоператоров вместо caseпозволяет мне использовать сопоставление регулярных выражений ( [[ "$cmd" =~ <REGEX> ]]), код ниже работает по следующей логике:

  • Не добавляйте информацию в историю в следующих случаях:
    • Командная строка начинается или заканчивается двумя или более последовательными пробелами.
      Пример:
      • <spc><spc>history
  • Добавьте команду, прокомментированную в следующем случае:
    • rmили mvкоманды с переключателями 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"

Связанный контент