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
}