Para evitar registrar comandos "peligrosos" en el historial de bash, agregué la siguiente línea a mi .bashrc
archivo:
HISTIGNORE='rm *:mv *:cp *:cat*>*:pv*>*'
Esto funciona bien, pero tiene un efecto secundario: no puedo ver el historial completo de comandos ejecutados en una máquina. Digamos que tengo varias máquinas para realizar experimentos y quiero poder ver todos los comandos ejecutados. Usaría bash internal history
para mostrar los comandos ejecutados y quizás grep para la fecha de hoy:
history | grep Sep-28
Lo que me gustaría tener es registrar también los comandos "peligrosos", pero poner un #
al principio de la línea, de modo que si ejecuto el comando del historial por error, no se produzca ningún daño.
No tengo idea si esto es posible.
Actualización y aclaración:
La razón principal por la que esto es un problema para mí es que normalmente estoy conectado a mi máquina desde varios terminales, y cualquier comando ejecutado en un terminal se lee inmediatamente en el historial de otros terminales. Esto se logra mediante
PROMPT_COMMAND="history -a; history -c; history -r"
Imaginemos que tengo dos terminales abiertas. En uno tengo algún cat /dev/foo > file.out
proceso ejecutándose. En el segundo, compruebo el progreso con ls -lAhF
. Sigo repitiendo ls
presionando Upy ENTER(es decir, último comando del historial). Tan pronto como finaliza el primer comando, el último comando del historial ya no es ls
, sino cat /dev/foo > file.out
. Si no tengo cuidado, iniciaré cat nuevamente y sobrescribiré file.out.
Lo que me gustaría lograr es que el comando cat esté precedido por un #
, para que no se ejecute. Sin embargo, todavía lo vería en el historial y puedo reutilizarlo (si es un comando largo) descomentándolo.
Respuesta1
No voy a responder exactamente a tu pregunta, pero tal vez te dé una solución alternativa a tu problema.
Si entiendo correctamente, le preocupan los errores que podría cometer al escribir, por ejemplo, !rm
si sucede que el rm
comando anterior en el historial elimina algo que le gustaría conservar.
En este caso, un bonitointentola opción es histverify
. Si shopt -s histverify
recuerdas un comando con el bang !
, no se ejecutará inmediatamente, sino que se cargará en la línea de lectura para que puedas decidir ejecutarlo o no, y esto también te da la posibilidad de editarlo.
Intentalo:
Sin
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
Con
histverify
:$ shopt -s histverify $ touch some_foo $ rm some_foo $ touch some_foo $ !rm $ rm some_foo <cursor here>
En este caso, tendrás el cursor al final de la línea, listo para ejecutarlo nuevamente (o no) o editarlo.
Si te gusta esta opción, ponla en tu .bashrc
:
shopt -s histverify
Respuesta2
Podrías hacer algo como:
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
La idea es que antes de cada mensaje, verifiquemos la última entrada del historial ( history 1
) y si es una de laspeligrosounos, lo borramos ( history -d
) y lo volvemos a agregar con un #
with history -s
.
(Obviamente, debes eliminar tu HISTIGNORE
configuración).
Sin embargo, un efecto secundario no deseado de esto es que altera latiempo de la historiade esos rm
, mv
... comandos.
Para solucionarlo, una alternativa podría ser:
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
Esta vez, registramos la hora del último historial y, para volver a agregar la línea del historial, utilizamos history -r
un archivo temporal (el documento aquí) que incluye la marca de tiempo.
Desea que fixhist
se realice antes que su history -a; history -c; history -r
. Desafortunadamente, la versión actual bash
tiene un error que history -a
no guarda la línea adicional que agregamos. Una solución alternativa es escribirlo en su lugar:
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
Eso es agregar el comando comentado al HISTFILE nosotros mismos en lugar de dejar que history -a
lo hagamos.
Respuesta3
Elrespuesta aceptada de Stéphanees absolutamente asombroso. A continuación se muestra mi adopción, que hace que la combinación de patrones sea un poco diferente.
Usar if
declaraciones en lugar de case
me permite usar coincidencias de expresiones regulares ( [[ "$cmd" =~ <REGEX> ]]
), el siguiente código funciona con la siguiente lógica:
- No agregue al historial en el siguiente caso:
- La línea de comando comienza o termina con 2 o más espacios consecutivos
Ejemplo:<spc><spc>history
- La línea de comando comienza o termina con 2 o más espacios consecutivos
- Agregar comando comentado en el siguiente caso:
rm
omv
comandos con interruptores cli- La línea de comando comienza con
:;
o un espacio simple o termina con;:
Ejemplos:
rm -R /tmp/somedir
echo "doing something dangerous I don't want to trip by accident";:
Esto también tiene cavieats, con los que estoy de acuerdo, por ejemplo:
echo ";rm -something"
- De lo contrario, agregar al historial
. Ejemplos:rm somefile
echo ";rm -something"
El código
_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"