Diary

Diary

日々学んだことをアウトプットする場として初めてみました

zsh: history から失敗したコマンドを削除する

課題

terminal 上のコマンドは実行履歴が残っており、それを shell の history と呼ぶのですが、そんな history は情報の山となってます。

自分は主に『過去のコマンドを history から呼び出し fzf で曖昧に検索する』みたいな用途で使っているのですが、その際に『typo などで絶対に成功しないコマンドが永遠に history に残り、レコメンドに表示されてしまう』というという問題がありました。


(過去の typo である dockr ps は不要すぎる!)

そこで『zsh の precmd(プロンプトを表示する直前に呼ばれる)を利用し、終了コードが 0 より大きかったら履歴から削除する』というスクリプトを作成したので共有です。

環境

$ zsh --version
zsh 5.8.1 (x86_64-apple-darwin22.0)

# 今回のスクリプトでは omz_history への alias になってても大丈夫なはず。
$ which history
history: shell built-in command

結論

.zshrc に以下の設定を書くことで対応したつもりです。

  • 失敗直後、HISTFILE の最終行だけ消す
  • history は常に HISTFILE を指してるわけではない
    • HISTFILE をいじる前にシェルに持ってる情報を全部 HISTFILE に書き出す
    • fzf での検索前に HISTFILE と history コマンドの同期をとる
  • ファイルの内容・プロセスの状況等でたまたま失敗したものは削除したくない
    • HISTFILE に残す内容に重複を許し、失敗時は失敗したもの(最後の1行)のみを削除することで対応
      • 1度でも成功したら残る
# history of zsh
setopt HIST_EXPIRE_DUPS_FIRST
setopt HIST_IGNORE_DUPS
# NOT set this flag in order to keep past successful commands (and can be searched).
# setopt HIST_IGNORE_ALL_DUPS
setopt HIST_IGNORE_SPACE
setopt HIST_FIND_NO_DUPS
setopt HIST_SAVE_NO_DUPS
setopt HIST_REDUCE_BLANKS
setopt EXTENDED_HISTORY
setopt HIST_NO_STORE
setopt SHARE_HISTORY

HISTFILE="$HOME/.zsh_history"
HISTSIZE=50000
SAVEHIST=10000

HISTORY_IGNORE="(vz|sz|cz|ls|cd|pwd|exit|cd ..|last_command=*|grep -vxF*|sed '$d' $HISTFILE*)"

precmd() {
    if [[ $? != 0 ]]; then
        # Write to $HISTFILE immediatlly.
        fc -W

        # Delete from $HISTFILE when the last command fails.
        # version1
        # last_command="$(tail -n1 $HISTFILE)";
        # grep -vxF "$last_command" $HISTFILE > temp_histfile && mv temp_histfile $HISTFILE
        # version2
        sed '$d' $HISTFILE > temp_histfile && mv temp_histfile $HISTFILE

        # NOT updating the history command here in order to load the previous command using the up arrow key.
        # fc -R $HISTFILE
    fi
}

select-history() {
    # Load from $HISTFILE (update history command).
    fc -R $HISTFILE

    # Write to command-line.
    # BUFFER="$(HISTCONTROL=ignoredups; history -n -r 1 | fzf --query "$BUFFER")"
    BUFFER="$(history | awk '{for(i=2;i<=NF;++i) printf "%s ", $i; printf "\n"}' | sort | uniq | fzf --query "$BUFFER")"

    # Move cursor to the right end of the command-line.
    CURSOR="$#BUFFER"
}
# Assign Ctrl + R.
zle -N select-history
bindkey '^r' select-history