bash_history: comment out dangerous commands: `#`

I'm not going to exactly answer your question, but maybe give you an alternate solution to your problem.

If I understand correctly you're concerned about mistakes you could make by typing, e.g., !rm if it happened that the previous rm command in history removes something you'd like to keep.

In this case, a nice bash option is histverify. If you shopt -s histverify, and if you recall a command with the bang !, it will not execute immediately, but be loaded in the readline so that you can either decide to execute it or not, and this also gives you the possibility to edit it.

Try it:

  • Without 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
    
  • With histverify:

    $ shopt -s histverify
    $ touch some_foo
    $ rm some_foo
    $ touch some_foo
    $ !rm
    $ rm some_foo <cursor here>
    

    In this case you'll have the cursor at the end of the line, ready to launch it again -- or not -- or to edit it.

If you like this option, put it in your .bashrc:

shopt -s histverify

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.