漂亮的命令行-3:fzf:命令行中的everything

环境

  • 系统:ArmBian(Debian)
  • Cpu架构:arm64

效果

1 什么是fzf?

项目地址GitHub - junegunn/fzf: :cherry_blossom: A command-line fuzzy finder

fzf 是一个用 Go 语言编写的通用命令行模糊查找工具,广泛应用于 Unix 类操作系统(如 Linux 和 macOS)中。它的核心功能是提供一种高效的方式来在大量文本数据中快速查找并筛选出目标数据。与其他查找工具相比,fzf 以其模糊匹配能力和易用性而著称

  1. 模糊匹配

fzf 可以在输入的每个字符与候选项的每个字符之间进行模糊匹配,不要求精确匹配。这使得用户能够更快地找到目标项,即使输入不完整或不准确。

  1. 实时过滤

fzf 在用户输入查询字符串时实时更新匹配结果。这种即时反馈大大提高了用户的使用效率和体验。

  1. 交互式界面

fzf 提供了一个交互式的文本用户界面,用户可以通过键盘快捷键快速导航和选择匹配结果。

  1. 可定制性(可以预览文件内容

fzf 允许用户通过环境变量和命令行参数进行广泛的定制,适应不同的使用场景和需求。

2 安装

使用git仓库中的安装方式(优点:比软件包的fzf版本更加新)

# 存储在~/.fzf
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
# 安装fzf
~/.fzf/install
  • ~/.fzf/install做了2件事

    1. 生成配置文件~/.fzf.zsh

    2. 在shell(zsh)中集成fzf:[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh

【可选】升级

cd ~/.fzf && git pull && ./install

3 使用fzf(这里只做了解)

fzf共有3种使用方式

不建议用-方式1:命令行直接输入

# 一般不用
fzf

不建议用-方式2:调用fzf的快捷键(这是fzf预设的快捷键,也可以更改)

  • 搜索文件 + 目录:Ctrl + T

  • 只搜索目录:Alt + C

  • 只搜索命令历史:Ctrl + R(建议用)

建议使用-方式3:补全快捷键

# 默认快捷键(可以更改)
# 按2次**,在按1次tab键
**<tab>
# 例如
cd **<tab>
vim **<tab>

fzf的可配置比较强,接下来我们配置fzf,用的更加舒服

4 配置fzf

4.1 安装fdfind

为什么要安装fdfind?

fzf默认使用find命令,但我是arm机器,比较慢,fd 是用 Rust 编写的,它在性能上有明显优势,特别是在处理大目录树和大量文件时,搜索速度比 find 更快。此外,fd 在多线程查找方面表现优秀

安装

apt-get install fd-find

更多fd示例(中文)cha0ran/fd-zh: fd Chinese document

4.2 安装batcat

项目地址bat/doc/README-zh.md at master · sharkdp/bat · GitHub

什么是batcat?

batcat应该算是cat命令的上位替代,可以代码高亮
image

为什么要安装batcat?
fzf的预览文件功能默认使用cat,cat是不提供高亮的,使用batcat可以和fzf集成,让fzf可以预览文件的时候提供代码高亮功能

安装

apt-get install bat

4.3 配置fzf

4.3.1 解释配置

PS:因为fzf有3种使用方式,所以对应着3种使用方式的配置

如下是我使用的配置(需要安装 fdfind、batcat)

俯瞰 fzf 的配置(结构化一下)

4.3.1.1 与输入"fzf"回车相关(没啥用

  • fdfind:使用fdfind搜索文件
  • --hidden:fdfind的搜索隐藏文件选项
  • --exclude ".git":fdfind的排除的目录选项
  • . ~:搜索当前目录.与root目录~
# 默认输入"fzf"回车之后的命令
export FZF_DEFAULT_COMMAND='fdfind --hidden --follow --exclude ".git" --exclude "node_modules" . ~'

4.3.1.2 与fzf 的快捷键(Ctrl+T、Alt+C、Ctrl+R)相关(没啥用

  • --preview 'bat -n --color=always {}'使用bat预览文件
  • --preview 'lsd --tree {}'使用lsd --tree预览目录结构
# 配置:fzf 的快捷键(Ctrl+T、Alt+C、Ctrl+R)

# Ctrl+T
export FZF_CTRL_T_COMMAND='fdfind --hidden --follow --exclude ".git" --exclude "node_modules" . ~'
export FZF_CTRL_T_OPTS="
  --preview 'bat -n --color=always {}'
  --bind 'ctrl-/:change-preview-window(down|hidden|)'"

# Alt+C
export FZF_ALT_C_COMMAND="$(FZF_SEARCH_TEMPLATE --type d)"
export FZF_ALT_C_COMMAND='fdfind --type d --hidden --follow --exclude ".git" --exclude "node_modules" . ~'
export FZF_ALT_C_OPTS="
  --preview 'lsd --tree {}'"

# Ctrl+R
export FZF_CTRL_R_OPTS="
  --preview 'echo {}' --preview-window up:3:hidden:wrap
  --bind 'ctrl-/:toggle-preview'
  --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
  --color header:italic
  --header 'Press CTRL-Y to copy command into clipboard'"

4.3.1.3 与**<tab>快捷键补全相关

  • FZF_COMPLETION_TRIGGER:更改fzf的补全快捷键
# 配置:fzf 的【**快捷键】补全

# 更改默认 **<tab> 为 `<tab>
export FZF_COMPLETION_TRIGGER='`'
# 【样式】调整
export FZF_COMPLETION_OPTS='--border --info=inline'
# **快捷键进行【命令行】补全,调用此函数
_fzf_compgen_path() {
  fdfind --hidden --follow --exclude ".git" --exclude "node_modules" . ~ "$1"
}
# **快捷键进行【路径】补全,调用此函数
_fzf_compgen_dir() {
  fdfind --type d --hidden --follow --exclude ".git" --exclude "node_modules" . ~ "$1"
}
# **快捷键,根据命令名称进行【任务分配】
_fzf_comprun() {
  local command=$1
  shift

  case "$command" in
    # cd命令,使用该配置
    cd) fzf --preview 'lsd --tree {} | head -200' "$@" ;;
    # export、unset使用该配置
    export|unset) fzf --preview "eval 'echo \$'{}" "$@" ;;
    # ssh使用
    ssh) fzf --preview 'dig {}' "$@" ;;
    # 其他命令,使用该配置
    *) fzf --preview 'batcat -n --color=always {}' "$@" ;;
  esac
}

4.3.2 使用优化后的配置

上述配置的缺点

  1. 重复

仔细观察fzf的配置就会发现,有大量的重复代码(因为fzf有3种使用方式,如果需要3种使用方式的效果表现一致,就需要3种方式都使用相同的代码)

  1. 因为重复所以,不方便修改fzf配置

需要修改的地方较多,容易出错

优化后的配置(可以直接使用)

  1. 修改zsh
vim ~/.zshrc
  1. 追加如下内容,保存退出
# 定义搜索路径
search_paths=(
  $HOME
  # 当前目录
  .
)

format_paths() {
  local paths=("$@")

  # 定义标志变量,用于检测是否包含 `/` 字符
  contains_slash=false

  # 遍历数组,检查是否包含 `/` 字符
  for path in "${paths[@]}"; do
    if [[ "$path" == *"/"* ]]; then
      contains_slash=true
      break
    fi
  done

  # 如果包含 `/` 字符,则在索引0的位置添加 `.` 字符
  # 为什么要加 `.`? 参考:https://github.com/sharkdp/fd/pull/913
  # 路径中,存在'/',而'/'在正则中使用,例如为了匹配/root目录,在fd中需要使用 `. /root`
  if $contains_slash; then
    paths=("." "${paths[@]}")
  fi

  # 格式化搜索路径:使用 printf 将数组元素合并成一个字符串转换为用空格分隔的字符串
  local formatted=$(printf "%s " "${paths[@]}")

  echo "$formatted"
}

# 调用函数并存储搜索路径的结果
formatted_paths=$(format_paths "${search_paths[@]}")

# 目录黑名单
ignore_paths=(
  .git
  node_modules
  __pycache__
)
get_ingored_paths() {
  case "$1" in
    "fdfind"|"fd") ignore_flag="--exclude" ;;
    "find") ignore_flag="-I" ;;
    "lsd") ignore_flag="-I" ;;
    # 默认情况下,ignore_flag 为空
    *) ignore_flag="" ;;
  esac

  local result=""
  for path in "${ignore_paths[@]}"; do
    result="$result $ignore_flag \"$path\""
  done
  echo "$result"
}

# 定义通用的 fzf 搜索函数
FZF_SEARCH_TEMPLATE() {
  # 完整命令为:fdfind --hidden --follow --exclude ".git" --exclude "node_modules" . /root .
  local result="fdfind --hidden --follow $@ $(get_ingored_paths fdfind) $formatted_paths"
  echo $result
}
preview_opts="--preview='(
    batcat -n --color=always {} ||
    bat -n --color=always {} ||
    cat {} ||
    lsd --tree $(get_ingored_paths lsd) {}
  ) 2>/dev/null | head -n 100'
"

# 默认输入"fzf"回车之后的命令
export FZF_DEFAULT_COMMAND="$(FZF_SEARCH_TEMPLATE)"
export FZF_DEFAULT_OPTS="
  --height 40%
  --layout=reverse
  --border
  $preview_opts
"
# 配置:fzf 的 【Ctrl+T、Alt+C】快捷键
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_CTRL_T_OPTS="
  $preview_opts
  --bind 'ctrl-/:change-preview-window(down|hidden|)'"
export FZF_ALT_C_COMMAND="$(FZF_SEARCH_TEMPLATE --type d)"
export FZF_ALT_C_OPTS="$FZF_DEFAULT_OPTS"
export FZF_CTRL_R_OPTS="
  --preview 'echo {}' --preview-window up:3:hidden:wrap
  --bind 'ctrl-/:toggle-preview'
  --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
  --color header:italic
  --header 'Press CTRL-Y to copy command into clipboard'"

# 配置补全:fzf 的【**快捷键】
export FZF_COMPLETION_TRIGGER='`'
# 补全调整
export FZF_COMPLETION_OPTS="$FZF_DEFAULT_OPTS"
# **快捷键进行【命令行】补全,调用此函数
_fzf_compgen_path() {
  # 需要使用 eval 执行字符串中的命令
  # fzf会传递 当前目录,不接收该参数,因为在 search_paths 已经手动定义
  # eval "$FZF_CTRL_T_COMMAND $1"
  eval "$FZF_CTRL_T_COMMAND"
}
# **快捷键进行【路径】补全,调用此函数
_fzf_compgen_dir() {
  # eval "$FZF_ALT_C_COMMAND $1"
  eval "$FZF_ALT_C_COMMAND"
}
# **快捷键,为【特定命令】,添加【额外参数】
_fzf_comprun() {
  local command=$1
  shift
  case "$command" in
    cd)     eval "fzf $preview_opts"               "$@" ;;
    export|unset) fzf --preview "eval 'echo \$'{}" "$@" ;;
    ssh)          fzf --preview 'dig {}'           "$@" ;;
    *)      eval "fzf $preview_opts"               "$@" ;;
  esac
}
  1. 生效
source ~/.zshrc

将经常修改的配置都抽取为变量了

  • 指定搜索的路径:search_paths

    • 因为fzf默认只会搜索当前目录,非常不方便。例如:在nginx/logs目录,只能搜索logs目录的内容,无法搜索nginx/conf.d的配置文件
    • 这里,我默认设置为.(当前目录)、~(root)目录,这两个目录是必定搜索的,兄弟萌可以按需增加目录
  • 忽略的目录:ignore_paths

    • 需要忽略的目录,兄弟萌可以按需增加目录
  • 修改fzf调用fdfind的命令:FZF_SEARCH_TEMPLATE

  • fzf的额外选项:FZF_DEFAULT_OPTS

  • fzf的预览选项:preview_opts

  • 更改fzf的补全快捷键:FZF_COMPLETION_TRIGGER(已经修改为`<tab>)

以上配置都可以通过 官方 找到出处

5 使用fzf

如果你的配置和我一致,那么

  • PS:预览窗口可以使用鼠标滚轮
vim `<tab>

cd `<tab>

Ctrl + R:搜索历史命令

17 Likes

:+1:

3 Likes

学到了 :stuck_out_tongue_closed_eyes:

2 Likes

感谢分享:+1:

太强了

大佬牛

不错 不错 正好需要

很棒,而且非常推荐使用batcat

mark 慢慢看

真的挺不错的,配置完好看多了

日后再来美化了