Bash Command-History – Comment Out Dangerous Commands in bash_history

bashcommand history

To prevent logging "dangerous" commands in bash history, I have added following line to my .bashrc file:

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

this works well, but it has a side effect: I cannot see the complete history of commands executed on a machine. Let's say I have several machines for experiments, and I want to be able to see all commands executed. I would use the bash internal history to display executed commands, and perhaps grep for today's date:

history | grep Sep-28

What I would like do have is to log "dangerous" commands as well, but put a # at the beginning of the line, so that if I happen to execute the command from history by mistake, no damage would be done.

I have no idea if this is possible.

Update and clarification:

The main reason why this is a problem for me is that I am usually connected to my machine from several terminals, and any command executed on one terminal is immediately read into history of other terminals. This is achieved by

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

Let's imagine I have two terminals open. In one I have some cat /dev/foo > file.out process running. In the second, I check the progress with ls -lAhF. I keep repeating ls by pressing Up and ENTER (that is, last command from history). As soon as the first command finishes, the last command from history is no longer ls, but cat /dev/foo > file.out. If I am not careful, I will start cat again and overwrite file.out.

What I would like to achieve is that the cat command would be preceded with a #, so that it would not be executed. I would, however, still see it in history and can reuse it (if it is a long command) by un-commenting it.

Best Answer

You could do something like:

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

The idea being that before each prompt, we check the last history entry (history 1) and if it's one of the dangerous ones, we delete it (history -d) and add it back with a # with history -s.

(obviously, you need to remove your HISTIGNORE setting).

An unwanted side effect of that though is that it alters the history time of those rm, mv... commands.

To fix that, an alternative could be:

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

This time, we record the time of the last history, and to add back the history line, we use history -r from a temporary file (the here document) that includes the timestamp.

You'd want the fixhist to be performed before your history -a; history -c; history -r. Unfortunately, the current version of bash has a bug in that history -a doesn't save that extra line that we've added. A work around is to write it instead:

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

That is to append the commented command to the HISTFILE ourselves instead of letting history -a do it.

Related Question