Чтобы предотвратить запись «опасных» команд в историю 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"