如何使用 fzf 浏览历史记录时删除历史记录行

如何使用 fzf 浏览历史记录时删除历史记录行

bash 历史记录中经常有一些我不再需要的项目和/或太长的项目,以至于当与 fzf 一起使用时,几乎可以匹配任何输入的文本,这使得历史记录上的模糊匹配有点无用。所以我正在寻找一种能够在使用 fzf 查看历史记录时删除这些行的方法,但这太天真了(而且错过了重新加载,但这不是这里的问题):

export FZF_CTRL_R_OPTS="--bind 'ctrl-d:execute-silent(history -d {1})'"

它不会删除任何历史记录,我假设这可能是因为 fzf 运行了类似的命令$SHELL -c "history -d 1000",但这是一个新的 shell(我的 $SHELL 是 /bin/bash),它没有历史记录(或者至少不是当前 shell 的历史记录)。这个假设正确吗?如何解决这个问题?

答案1

一种方法(如下所示,将其作为别名放入 .bashrc 中)是直接编辑 .bash_history 文件,使用 fzf 选择命令,然后在编辑后重新加载它:

alias histfzf='history -w; cat .bash_history | fzf > /tmp/to_remove; grep -vxFf /tmp/to_remove .bash_history > .new_bash_history; mv .new_bash_history .bash_history; rm /tmp/to_remove; history -r'

每个步骤的解释:

# write active history in memory to the .bash_history file
history -w;

# select commands from history (using fzf), and store them in a file:
cat .bash_history | fzf > /tmp/to_remove

# Find commands that don't appear in the to_remove list (and store in .new_bash_history:
grep -vxFf /tmp/to_remove .bash_history > .new_bash_history

# overwrite the history with the edited version:
mv .new_bash_history .bash_history;

# clean up:
rm /tmp/to_remove;

# reload history:
history -r

答案2

感谢 mattb 的想法,我目前在 .bashrc 中使用它,并添加了评论。

# Separate so that should fzf's __fzf_history__ implementation change I can just copy-paste
# from it into this file without having to put my stuff in between.
# {+f2..} = fzf will replace this with path to a file containing
#           all selected items starting at second field i.e. the actual history text
export FZF_CTRL_R_OPTS="--bind 'ctrl-s:clear-selection,ctrl-d:execute-silent(grep -vxFf {+f2..} ~/.bash_history > ~/.edh; cp ~/.edh ~/.bash_history)'"

__fzf_history__() {
  # Flush to .bash_history
  history -w
  # Same as in fzf's original implementation, except that +m is replaced with -m
  # to allow selection of multiple items.
  local output
  output=$(
    builtin fc -lnr -2147483648 |
      last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
      FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-60%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS -m --read0" $(__fzfcmd) --query "$READLINE_LINE"
  )
  # Reload from .bash_history if needed.
  if [ -f ~/.edh ]; then
    history -r
    rm ~/.edh
  fi
  # Rest of original implementation
  if [ -z "$output"]; then
    return
  fi
  READLINE_LINE=${output#*$'\t'}
  if [ -z "$READLINE_POINT" ]; then
    echo "$READLINE_LINE"
  else
    READLINE_POINT=0x7fffffff
  fi
}

# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'

除了重新加载视图外,它完成了我想要的一切,但这需要将大部分庞大的命令放在 fzf 命令行中,所以我现在不想去那里 :)

答案3

为了让你的 bash 历史记录中不包含无用的命令,还有一件事可能会有用,那就是HISTIGNORE在你的 .bashrc 中设置变量:

HISTIGNORE="cd:cl:clc:ls:l:ll:lll:..:...:....:sob:f *"

您不想记录在历史记录中的每个命令都以冒号分隔。您还可以使用正则表达式模式来过滤掉这些命令。

我过滤掉的许多命令都是我拥有的别名,我从来不会回头搜索来重复它们:

# clear the terminal (clc is because I did too much matlab)
alias cl='clear && clear'
alias clc='clear && clear'

# show ls vertically by default using the -1 switch
alias l='ls -1 -hF'
# same as above, but with file details too
alias ll='ls -lhF'
# same as above, but with hidden files too
alias lll='ls -alhF'

# move up 1,2 or 3 directories
alias ..='cd ../'
alias ...='cd ../../'
alias ....='cd ../../../'

# source .bashrc
alias sob='source ~/.bashrc'

最后一个命令f *是我编写的函数(请参阅此处的答案) 使用 fzf 从我的系统中选择文件并将它们作为参数传递给任何程序(例如 vim、cat、vlc、evince 等)。当我第一次编写它时,我的历史记录中充满了诸如f vim和之类的东西f cd- 后来对我来说完全没用。从那时起,我改进了该功能,以便它实际上将 fzf 扩展命令放入历史记录中,所以我现在不需要过滤f *,但它有助于说明如何使用 regexp 来忽略某些命令。

如果您对我提到的 fzf 函数感兴趣,它在这里:

#!/bin/bash

# Run command/application and choose paths/files with fzf.
# Always return control of the terminal to user (e.g. when opening GUIs).
# The full command that was used will appear in your history just like any
# other (N.B. to achieve this I write the shell's active history to
# ~/.bash_history)
#
# Usage:
# f cd [OPTION]... (hit enter, choose path)
# f cat [OPTION]... (hit enter, choose files)
# f vim [OPTION]... (hit enter, choose files)
# f vlc [OPTION]... (hit enter, choose files)

f() {
    # Store the program
    program="$1"

    # Remove first argument off the list
    shift

    # Store option flags with separating spaces, or just set as single space
    options="$@"
    if [ -z "${options}" ]; then
        options=" "
    else
        options=" $options "
    fi

    # Store the arguments from fzf
    arguments=($(fzf --multi))

    # If no arguments passed (e.g. if Esc pressed), return to terminal
    if [ -z "${arguments}" ]; then
        return 1
    fi

    # We want the command to show up in our bash history, so write the shell's
    # active history to ~/.bash_history. Then we'll also add the command from
    # fzf, then we'll load it all back into the shell's active history
    history -w

    # ADD A REPEATABLE COMMAND TO THE BASH HISTORY ############################
    # Store the arguments in a temporary file for sanitising before being
    # entered into bash history
    : > /tmp/fzf_tmp
    for file in "${arguments[@]}"; do
        echo "$file" >> /tmp/fzf_tmp
    done

    # Put all input arguments on one line and sanitise the command by putting
    # single quotes around each argument, also first put an extra single quote
    # next to any pre-existing single quotes in the raw argument
    sed -i "s/'/''/g; s/.*/'&'/g; s/\n//g" /tmp/fzf_tmp

    # If the program is on the GUI list, add a '&' to the command history
    if [[ "$program" =~ ^(nautilus|zathura|evince|vlc|eog|kolourpaint)$ ]]; then
        sed -i '${s/$/ \&/}' /tmp/fzf_tmp
    fi

    # Grab the sanitised arguments
    arguments="$(cat /tmp/fzf_tmp)"

    # Add the command with the sanitised arguments to our .bash_history
    echo $program$options$arguments >> ~/.bash_history

    # Reload the ~/.bash_history into the shell's active history
    history -r

    # EXECUTE THE LAST COMMAND IN ~/.bash_history #############################
    fc -s -1

    # Clean up temporary variables
    rm /tmp/fzf_tmp
}

相关内容