28

Sure there's a super simple smart way to do this, but since history in zshell seems to be aliased to fc, there's no way to use the tricks mentioned in How to remove a single line from history?.

Any pointers on how to do this? My use case would be to remove the last issued command from history so it stops autocompleting (mostly when you've mistyped something and it keeps showing up).

I know I can get the last issued command by doing

history | tail -n 1, but history -d doesn't work and I can't find proper documentation for zsh.

1
  • In macOS, I can open in a text editor the file ~.zsh_history by typing open ~/.zsh_history, and then just delete the desired line. In Linux, you could probably do the same thing but "replace open with the name of your preferred text editor, such as nano, vim, or gedit." (Answer modified from Pot '16.) Jun 15, 2022 at 2:49

7 Answers 7

12

Zsh does not offer a real history editing facility. The command history is basically read-only. All you can do is replace it wholesale.

To edit the command history:

  1. Save it to a file: fc -W
  2. Edit the file
  3. Load the history from the file: fc -R

You can choose the file name by setting HISTFILE.

Untested code:

remove_last_history_entry () {
  setopt local_options extended_history no_hist_save_no_dups err_return
  
  local HISTFILE=~/.zsh_history_tmp_$$ SAVEHIST=$HISTSIZE
  fc -W
  ed -s $HISTFILE <<EOF >/dev/null
d
w
q
EOF
  fc -R
}
2
  • Can you restrict operations with fc to the session history, or does it use HISTFILE?
    – Seamus
    Aug 5, 2020 at 10:15
  • Zsh might not offer you real history editing tools, but I do offer real history editing tools for Zsh: unix.stackexchange.com/a/605328/413610 Nov 20, 2020 at 11:59
8

Usually you can just remove the line from ~/.zsh_history, but if you cannot find it look in ~/.zshrc for the location of your history file.

Then use any text editor of your choice to remove the offending line(s). Alternatively, just delete the whole file and close all zsh instances and start a new one. History purged!

7

So yeah, this is an old topic, but none of these questions answered it for me so I just spent ages trying to figure this out too!

Here's My solution with thanks from @Gilles for the hint of using 'fc -R' and 'fc -W' :).

Paste the script below this into your .zshrc file.

reload with source .zshrc

then type 'forget' to forget the last command :D. type 'forget 3' to forget the last 3 commands. Some sexy shizz.

Pressing the up arrow will take you straight to the last command and won't remember the word 'forget' either :).

Updated: Added home path, so it works in all directories now lol.

Update 2: Added the ability to pass the number of last commands you want to forget :D. Try 'forget 2' to forget the last 2 commands :D.

# Put a space at the start of a command to make sure it doesn't get added to the history.
setopt histignorespace

alias forget=' my_remove_last_history_entry' # Added a space in 'my_remove_last_history_entry' so that zsh forgets the 'forget' command :).

# ZSH's history is different from bash,
# so here's my fucntion to remove
# the last item from history.
my_remove_last_history_entry() {
    # This sub-function checks if the argument passed is a number.
    # Thanks to @yabt on stackoverflow for this :).
    is_int() ( return $(test "$@" -eq "$@" > /dev/null 2>&1); )

    # Set history file's location
    history_file="${HOME}/.zsh_history"
    history_temp_file="${history_file}.tmp"
    line_cout=$(wc -l $history_file)

    # Check if the user passed a number,
    # so we can delete x lines from history.
    lines_to_remove=1
    if [ $# -eq 0 ]; then
        # No arguments supplied, so set to one.
        lines_to_remove=1
    else
        # An argument passed. Check if it's a number.
        if $(is_int "${1}"); then
            lines_to_remove="$1"
        else
            echo "Unknown argument passed. Exiting..."
            return
        fi
    fi

    # Make the number negative, since head -n needs to be negative.
    lines_to_remove="-${lines_to_remove}"

    fc -W # write current shell's history to the history file.

    # Get the files contents minus the last entry(head -n -1 does that)
    #cat $history_file | head -n -1 &> $history_temp_file
    cat $history_file | head -n "${lines_to_remove}" &> $history_temp_file
    mv "$history_temp_file" "$history_file"

    fc -R # read history file.
}

So there's a few things going on here. This command will let us type a space before any command, and it'll not be added to history.

setopt histignorespace

So we can press the space bar, and type 'echo hi', press enter and when we press the up arrow, 'echo hi' is not in our history :).

Notice how the alias 'forget' has a space before my_remove_last_history_entry. This is so that zsh doesn't save our 'forget' to history.

The function explained

ZSH uses fc for the history or something, so we do 'fc -W' to write our current commands to the history file, them we use 'head -n -1' to trim the last command off the file. We save that output to a temporary file and then replace the original history file with the temp one. And finally reload the history with fc -R.

There is however a problem with the function which is fixed with the alias.

If we run the function by it's name, it'll remove the last command, which is the call to the function. This is why we use the alias with a call to it using a space, so that zsh doesn't add this function name to the history file, making the last entry the one we want :D.

5
  • what if the last command was multi-line?
    – avp
    Dec 2, 2019 at 9:09
  • This should be voted as the official answer. Thanks.
    – gsl
    Jan 16, 2020 at 18:01
  • Be careful, head -n -1 doesn't work on macOS Mojave and you'll lose your .zsh_history file.
    – cdeutsch
    Jun 16, 2020 at 16:02
  • This cat command worked for me on macOS cat $history_file | head -n $(($(wc -l < $history_file) - $lines_to_remove)) &> $history_temp_file (I realize this is Unix and Linux StackExchange but could help someone)
    – cdeutsch
    Jun 16, 2020 at 16:13
  • to account for a custom path to the history file, set history_file="${HISTFILE:-${HOME}/.zsh_history}"
    – Enno
    Aug 5, 2020 at 6:37
6

This function will remove any one line you want from your Zsh history, no questions asked:

# Accepts one history line number as argument.
# Alternatively, you can do `dc -1` to remove the last line.
dc () {
  # Prevent the specified history line from being saved.
  local HISTORY_IGNORE="${(b)$(fc -ln $1 $1)}"

  # Write out the history to file, excluding lines that match `$HISTORY_IGNORE`.
  fc -W

  # Dispose of the current history and read the new history from file.
  fc -p $HISTFILE $HISTSIZE $SAVEHIST

  # TA-DA!
  print "Deleted '$HISTORY_IGNORE' from history."
}

If you want to additionally prevent all dc commands from being written to history, add the following in your ~/.zshrc file:

zshaddhistory() {
 [[ $1 != 'dc '* ]]
}

Update

I've now published a more comprehensive solution as a plugin: https://github.com/marlonrichert/zsh-hist

Usage examples

# Fix (cut from history; paste into command line) the last history item:
hist f -1

# Delete all history items ending with a newline or a semicolon:
hist d $'*(\n|;)'

Installation

  1. cd to the dir where you keep your plugins.
  2. Clone the repo:
    git clone https://github.com/marlonrichert/zsh-hist
    
  3. source the plugin in your ~/.zshrc file:
    source path/to/zsh-hist.plugin.zsh
    
3
  • Nice! Example usage: hist d -1 @MarlonRichert, May I suggest adding installation instructions and some examples to README.md? For others: Install using git clone https://github.com/marlonrichert/zsh-hist ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-hist then add zsh-hist to the list of plugins in ~/.zshrc: plugins=(zsh-hist) Oct 16, 2020 at 23:04
  • @JonathanWheeler Your instructions are specific to OhMyZsh. I now added instruction that work for any Zsh installation. Oct 19, 2020 at 12:42
  • 1
    Ah yes, nice catch/fix! Also, I like your updated examples! It immediately helped me learn better techniques; I'm using this a lot. Cheers. Nov 27, 2020 at 21:35
0

I use a simplified version of @pandagoespoop's answer, and it's working fine for me (limitations: on linux, for single-line commands).

One important modification is to restrict the permissions on the newly created history file. Redirecting to a new file uses default permissions, which will allow group+others to read your history. Hence I added:

chmod 600 $history_temp_file

Full code:

setopt hist_ignore_space

alias forget=" forget_last_history_entry"
forget_last_history_entry() {
    history_file="${HISTFILE:-${HOME}/.zsh_history}"
    history_temp_file="${history_file}.tmp"

    fc -W # write current shell's history to the history file.

    # Get the files contents minus the last entry(head -n -1 does that)
    head -n -1 $history_file > $history_temp_file
    chmod 600 $history_temp_file
    mv "$history_temp_file" "$history_file"

    fc -R # read history file.
}

I would've made this a comment if I had the reputation.

-1

I found out on zsh terminal it is done using something like:

sed -i '.bak' '10d' .zsh_history 
You can improve this command to automatically remove the last command in the history file.
It's a good idea to delete empty lines in the zsh history first:
sed -i '.bak' '/^$/d' .zsh_history

The backup file ".bak" is only required for sed on macOS.

-2

In zsh, set the histignorespace option (set -o histignorespace). And in bash, add ignorespace or ignoreboth (some distributions already do that in the default ~/.bashrc) to $HISTCONTROL or set HISTIGNORE=' *', and then you can start the current command with a <space> to avoid recording it.

:
 : none
fc -nl -1

:

In zsh, you can still recall that previous command once, but it's not stored in the history.

1
  • 2
    This doesn’t address the question.
    – Chris Page
    Nov 12, 2019 at 20:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .