Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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. Any comment would be greatly appreciated.

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"

Lets 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.

share|improve this question
    
Instead of repeating your command manually, you can use watch ls -lAhF or while sleep 1; do ls -lAhf; done; instead of watching the file size, you can use pv /dev/foo > file.out (ivarch.com/programs/pv.shtml). –  deltab Mar 12 at 15:26

2 Answers 2

up vote 7 down vote accepted
+100

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.

share|improve this answer
    
can you please explain what cmd=${cmd#*[0-9] } does? And where exactly should I put fixhist in my PROMPT_COMMAND? At the moment, it already contains PROMPT_COMMAND="history -a; history -c; history -r" –  Martin Vegter Jan 19 at 21:47
    
what is the role of HISTTIMEFORMAT=/? I have already set export HISTTIMEFORMAT="%b-%d %H:%M ". By the way, when I add your code into my $HOME/.bashrc, nothing happens. –  Martin Vegter Jan 19 at 22:27
    
HISTTIMEFORMAT=/ is only for that one invocation of history to get an output like 123 /rm x where it's easier to extract the cmd. There was an issue with some versions of bash where $HISTCMD was bogus in that context, try the new version. –  Stéphane Chazelas Jan 19 at 22:38
    
still does not work. I am wondering, do the # in the code not act as comments in .bashrc? –  Martin Vegter Jan 20 at 11:59
1  
@MartinVegter, yes, for modified history entries, bash inserts a * after the history number which was not expected. Should be fixed now. –  Stéphane Chazelas Apr 18 at 13:41

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

Hope this helps! (if not, sorry for the noise).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.