6

In my related post many years ago, I found a solution how to comment out "dangerous" commands saved in bash history, so that I do not execute them accidentally.

What would be the best solution to implement the same in zsh ?

Does zsh provide some functionality which I could use for this purpose? I assume, zsh beieng more flexible, this should be easier in zsh.

For reference, this is what I have been using in bash (based on accepted answer from Stéphane Chazelas):

fixhist() {
   local cmd time histnum
   cmd=$(HISTTIMEFORMAT='<%s>' history 1)
   histnum=$((${cmd%%[<*]*}))
   time=${cmd%%>*}
   time=${time#*<}
   cmd=${cmd#*>}
   case $cmd in
     (cp\ *|mv\ *|rm\ *|cat\ *\>*|pv\ *|dd\ *)
       history -d "$histnum" # delete
       history -a
       [ -f "$HISTFILE" ] && printf '#%s\n' "$time" " $cmd" >> "$HISTFILE";;
     (*)
       history -a
   esac
   history -c
   history -r
}

UPDATE 2022-09-05:

The accepted solution works, but has unintended side effect. It messes up with insert-last-word keybinding. Here short illustration:

I use one of my "dangerous" commands:

rm zz

it has been added to history with a comment (as desired):

history
...
# rm zz

Lets just add another command to history

echo foo

And now when I want to cycle through the history with Alt+., I get following results:

echo <Alt> + .

foo
history
# rm zz

instead of being offered zz, I am being offered the whole commented command # rm zz.

How can I fix this ?

2
  • 1
    About your update: Why would scrolling back through history offer you the (last?) argument to the dangerous command? What would you expect if the dangerous command was rm xx yy zz and why? One could argue that the history should show commands rather than filenames, options or other fragments of past commands. Commented Sep 5, 2022 at 10:06
  • 1
    @Kusalananda - if last command was rm xx yy zz then it will be stored in history as # rm xx yy zz. Cycling back through history with Alt+. should offer me zz. Commented Sep 9, 2022 at 5:54

2 Answers 2

5

Sure, use the zshaddhistory hook function and disable regular history handling.

function zshaddhistory() {
  # defang naughty commands; the entire history entry is in $1
  if [[ $1 =~ "cp\ *|mv\ *|rm\ *|cat\ *\>|pv\ *|dd\ *" ]]; then
    1="# $1"
  fi
  # write to usual history location
  print -sr -- ${1%%$'\n'}
  # do not save the history line. if you have a chain of zshaddhistory
  # hook functions, this may be more complicated to manage, depending
  # on what those other hooks do (man zshall | less -p zshaddhistory)
  return 1
}

Tested thusly on zsh 5.0.8

% exec zsh
% echo good
good
% echo bad; rm /etc 
bad
rm: /etc: Operation not permitted
% history | tail -4
  299  exec zsh
  300  echo good
  301  # echo bad; rm /etc
  302  history | tail -4
%   

This appears to work with the extendedhistory option set, as well.

4
  • what exactly do you mean by "disable regular history handling" ? I have following options set in my .zshrc: sharehistory histignorespace histreduceblanks extended_history inc_append_history interactive_comments Commented Sep 28, 2016 at 6:31
  • Regular history handling is typically to write the history line into the history, unaltered. With the alterations here, this would result in two history entries, one commented out, and one not. This is probably not what you want, so disable regular history handling by returning 1 from the function. Commented Sep 28, 2016 at 13:50
  • thanks it works. But it breaks some other history functions, such as: setopt histignorespace and setopt histreduceblanks. Commented Oct 2, 2016 at 12:10
  • So what happens with a command like socat or ncat? I'm not familiar with zsh, but the above seems to me like it would comment those out too. Commented Oct 2, 2016 at 16:08
1

The following function is based on the thrig's one and it fixes histignorespace:

function zshaddhistory() {
  if [[ $1 =~ "^ " ]]; then
    return 0
  elif [[ $1 =~ "cp\ *|mv\ *|rm\ *|cat\ *\>|pv\ *|dd\ *" ]]; then
    1="# $1"
  fi
  # write to usual history location
  print -sr -- ${1%%$'\n'}
  # do not save the history line. if you have a chain of zshaddhistory
  # hook functions, this may be more complicated to manage, depending
  # on what those other hooks do (man zshall | less -p zshaddhistory)
  return 1
}

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.