Making zsh history conditional on command success

So I thought it would be useful to exclude failed commands from my on-disk zsh history, while still having them available in the in-memory history until the current shell exits. This means that when you’re trying to find the one magic incantation that works of some esoteric command that you haven’t used for years, you can just search for it in your history without fear of repeating old mistakes.

From my .zshrc:


# Prevent the command from being written to history before it's
# executed; save it to LASTHIST instead.  Write it to history
# in precmd.
#
# called before a history line is saved.  See zshmisc(1).
function zshaddhistory() {
  # Remove line continuations since otherwise a "\" will eventually
  # get written to history with no newline.
  LASTHIST=${1//\\$'\n'/}
  # Return value 2: "... the history line will be saved on the internal
  # history list, but not written to the history file".
  return 2
}

# zsh hook called before the prompt is printed.  See zshmisc(1).
function precmd() {
  # Write the last command if successful, using the history buffered by
  # zshaddhistory().
  if [[ $? == 0 && -n ${LASTHIST//[[:space:]\n]/} && -n $HISTFILE ]] ; then
    print -sr -- ${=${LASTHIST%%'\n'}}
  fi
}

I think this is pretty self-explanatory: before the command is executed (zshaddhistory), the history line is saved in a global variable before the command is executed and prevented from being written to the history file. Then after the command has executed (precmd), as long as the command had a successful status ($? == 0), also write it to the history file.

Gotchas:

  • having to force word splitting (${= } syntax), because without that, word-based history expansion like !$ expands to the entire command rather than just the last word.
  • removing line continuations with ${1//\\$'\n'/}. Otherwise, line continuations will get written to history as a single backslash, which will then prevent word splitting (think echo\ foo).
  • ignoring whitespace-only commands with ${LASTHIST//[[:space:]\n]/}.

Update 2021-04-03: made some improvements to deal with line continuations and empty commands

3 thoughts on “Making zsh history conditional on command success”

  1. I Added [ $LASTHIST != $'\n' ] to the list of check before writing to the history file, to prevent from writing the empty line
    it became :
    if [[ $? == 0 && -n $LASTHIST && $LASTHIST != $'\n' && -n $HISTFILE ]] ;

  2. Thanks! I updated the check to -n ${LASTHIST//[[:space:]\n]/} which is slightly more general.

Leave a Reply

Your email address will not be published. Required fields are marked *