Avoiding invalid commands in Bash history

To avoid having stuff like “ls” in my bash history, I added “ls” to the “HISTIGNORE” environment variable.
However, on late nights, the result of this, and my left and right hand becoming out of sync with each other when I’m tired, is that the entry “sl”, which really doesn’t do anything, started showing up in my history instead.

Now, invalid commands in bash result in two things. A message saying “command not found” and exit status 127.
The latter is interesting. We can use that to avoid having the invalid entries showing up in the history.

If you don’t already have a bash PROMPT_COMMAND set, add this to .bashrc:

PROMPT_COMMAND="mypromptcommand"
function mypromptcommand {
}

That function will now be run every time your prompt is about to appear. Within it, we put this:

local exit_status=$?
# If the exit status was 127, the command was not found. Let's remove it from history

local number=$(history 1)
number=${number%% *}
if [ -n "$number" ]; then
    if [ $exit_status -eq 127 ] && ([ -z $HISTLASTENTRY ] || [ $HISTLASTENTRY -lt $number ]); then
        history -d $number
    else
        HISTLASTENTRY=$number
    fi
fi

What this does:

  • Gets the exit status of the last command
  • Gets the number for the latest entry in your history, if there is one
  • Checks if that entry was set to anything
  • If it was, we check if the exit status and see if it’s 127. We also require the HISTLASTENTRY variable to be either unset or less than the number of the latest history entry. This means the command in question was either added to the history, increasing the number of entries already there, or it was the first command ever (empty history)
  • If the above is true, we delete the last history entry
  • Otherwise, we set the last history entry to the amount of entries we found, so we can check if it is increased next time.

2 Comments

  • One change to make it much faster:

    local number=$(history 1 | awk '{print $1}')

    Using “history 1” prints out the last item without printing the entirety of history and without invoking tail.

    This is a significant savings if you have a large history, e.g. HISTSIZE=10000

    • bolt says:

      This was in 2010. I didn’t know any better 🙂

      Still no reason to keep bad advice online, however, so sincerely thanks for pointing it out.

      I updated the post, both using “history 1” as you suggested, and cutting the result using Bash instead of invoking an external program.

      For the record, my previous (and, as Chip pointed out, heavy) command:
      local number=$(history | tail -n 1 | awk '{print $1}')

1 Trackback

Leave a Reply

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