macOS fzf 完全指南:从安装配置到 Git、代码搜索与高级交互
# macOS fzf 完全指南:从安装配置到 Git、代码搜索与高级交互
如果你经常在 macOS 上使用 iTerm2、zsh、Git、Vim/Neovim 或各种 CLI 工具,那么 fzf 基本属于非常值得配置的一类终端工具。
很多人第一次接触 fzf,只把它理解成“模糊搜索文件”。实际上更准确的理解是:
fzf 是一个通用的终端交互式选择器。
它可以把几乎任何命令产生的文本列表变成一个支持搜索、预览、多选、快捷键和动作绑定的终端 UI。
它的核心工作模型可以理解为:
任意命令产生候选数据
↓
fzf
↓
搜索 / 筛选 / 预览 / 多选
↓
输出选择结果 / 执行动作
2
3
4
5
6
7
例如:
printf '%s\n' apple banana orange | fzf
也可以是:
git branch | fzf
ps aux | fzf
fd --type f | fzf
rg "Player" | fzf
2
3
4
本文以 macOS + iTerm2 + zsh 为主要环境,并以现代 fzf 0.74.x 的功能为基础。
# 1. 安装 fzf
macOS 推荐直接使用 Homebrew:
brew install fzf
建议顺手安装几个经常和 fzf 配套使用的工具:
brew install fd ripgrep bat eza
它们分别负责:
| 工具 | 用途 |
|---|---|
fzf | 交互式模糊选择器 |
fd | 文件、目录搜索,通常比传统 find 更适合交互使用 |
ripgrep / rg | 高性能代码内容搜索 |
bat | 带语法高亮的文件预览 |
eza | 更现代的目录列表和目录树显示 |
安装后确认版本:
fzf --version
截至 2026-09-24,Homebrew 中的稳定版本为 0.74.4。
# 2. zsh Shell Integration
fzf 不只是一个单独执行的命令,它还可以集成到 zsh 中,提供快捷键和 **<Tab> 补全。
# 2.1 现代推荐配置
现代版本可以直接在 ~/.zshrc 中加入:
source <(fzf --zsh)
然后重新加载:
source ~/.zshrc
它会提供:
Ctrl-T 文件/目录选择
Ctrl-R 历史命令搜索
Alt-C 目录搜索并 cd
**<Tab> fuzzy completion
2
3
4
# 2.2 如果以前已经使用 ~/.fzf.zsh
一些较早版本的 fzf 安装脚本会生成:
~/.fzf.zsh
并在 ~/.zshrc 里加入:
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
这种配置并不代表已经失效。
只要它仍然正常加载 fzf 的 completion 和 key bindings,就完全可以继续使用。
检查:
bindkey | grep fzf
如果类似:
"^I" fzf-completion
"^R" fzf-history-widget
"^T" fzf-file-widget
"^[c" fzf-cd-widget
2
3
4
说明已经完整加载:
Ctrl-I / Tab → fzf completion
Ctrl-R → history
Ctrl-T → file
Alt-C → cd
2
3
4
# 不要重复加载两套 integration
如果旧配置已经工作,不建议同时再写:
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
source <(fzf --zsh)
2
否则可能出现重复注册 completion 或 widget 的情况。
可以继续保留旧方式;以后如果想清理配置,再统一迁移到:
source <(fzf --zsh)
即可。
# 3. 三个最常用快捷键
# 3.1 Ctrl-T:文件选择
在当前命令行里按:
Ctrl-T
fzf 会打开文件/目录选择界面,并把选择结果插入当前命令行。
例如先输入:
vim
然后按:
Ctrl-T
选择:
src/components/PlayerView.ts
最后命令行会变成:
vim src/components/PlayerView.ts
这个快捷键特别适合:
vim
nvim
code
cat
bat
open
cp
mv
rm
2
3
4
5
6
7
8
9
等需要输入路径的命令。
# 3.2 Ctrl-R:搜索历史命令
按:
Ctrl-R
可以通过 fzf 搜索 shell history。
相比传统 shell 的单条历史搜索,fzf 的优势是:
- 可以同时看到多条候选
- 支持模糊匹配
- 支持多关键词
- 可以快速重复很久以前执行过的命令
例如搜索:
docker compose
可以快速找到之前执行过的相关命令。
# 3.3 Alt-C / Option-C:搜索目录并 cd
fzf 默认绑定:
Alt-C
选择目录后直接执行:
cd <selected-directory>
在 macOS 上,这个快捷键非常容易遇到一个问题:
fzf 已经绑定成功,但 Option-C 没有触发。
先检查:
bindkey | grep fzf
如果已经看到:
"^[c" fzf-cd-widget
说明 fzf 本身没有问题。
^[c 的意思就是:
ESC + c
即终端里的 Alt-C / Meta-C。
问题通常来自 iTerm2 的 Option 键设置。
# 4. macOS / iTerm2 中配置 Option 为 Alt / Meta
macOS 的 Option 键默认主要用于输入特殊字符。
例如:
Option-C
可能输入:
ç
但终端程序期望收到的是:
ESC + c
也就是:
^[c
# 4.1 验证 Option-C 实际发送什么
在 iTerm2 中:
先按 Ctrl-V
再按 Option-C
2
如果显示:
ç
说明 Option 被 macOS 用作特殊字符输入。
如果显示类似:
^[c
则说明已经正确作为 Meta/Alt 发送。
# 4.2 iTerm2 推荐设置
打开:
iTerm2
→ Settings
→ Profiles
→ Keys
2
3
4
找到:
Left Option key
Right Option key
2
推荐设置:
Left Option → Esc+
Right Option → Normal
2
这样:
左 Option
↓
终端 Alt / Meta
↓
fzf / Vim / zsh / TUI 快捷键
2
3
4
5
而:
右 Option
↓
继续保留 macOS 原生特殊字符输入
2
3
这是比较平衡的一种设置。
# 4.3 这个设置会影响什么?
如果把左 Option 设置成 Esc+,那么:
Left Option + C
Left Option + B
Left Option + F
Left Option + D
...
2
3
4
5
都会变成终端的 Meta/Alt 组合键。
这不会影响:
Cmd-C
Cmd-V
Ctrl-C
Ctrl-R
Ctrl-T
Shift
2
3
4
5
6
真正改变的是该侧 Option 键的特殊字符输入行为。
这通常反而会让很多 CLI 软件里的:
Alt-B
Alt-F
Alt-J
Alt-K
Alt-C
Alt-Enter
2
3
4
5
6
等快捷键开始正常工作。
# 5. **<Tab>:fzf fuzzy completion
shell integration 还提供了 fuzzy completion。
例如:
vim **<Tab>
会打开文件选择。
搜索目录:
cd **<Tab>
父目录:
vim ../**<Tab>
Home:
vim ~/**<Tab>
指定前缀:
vim ../texture**<Tab>
还可以配合某些命令,例如:
ssh **<Tab>
甚至:
kill **<Tab>
具体 completion 行为会根据命令不同而有所区别。
# 6. fzf 基础使用模型
最简单:
fzf
如果没有显式输入源,现代 fzf 可以使用自己的 walker 来生成候选文件。
更常见的是通过管道:
command | fzf
例如:
printf '%s\n' apple banana orange | fzf
选择结果会输出到 stdout。
因此可以:
selected=$(printf '%s\n' apple banana orange | fzf)
echo "$selected"
2
3
这也是很多自定义 CLI 脚本的基础。
# 7. fzf 查询语法
fzf 不只是普通 substring search,它支持扩展搜索语法。
常用形式:
| 查询 | 含义 |
|---|---|
abc | 模糊匹配 |
'abc | 精确包含 |
^abc | abc 开头 |
abc$ | abc 结尾 |
!abc | 排除 abc |
abc def | AND |
abc \| def | OR |
例如:
.ts$ player !test
表示:
.ts 结尾
AND
包含 player
AND
不包含 test
2
3
4
5
特别适合大型代码仓库。
# 8. 默认界面配置
fzf 默认可以全屏打开,不过个人更推荐类似下拉面板的形式。
可以在 ~/.zshrc 中配置:
export FZF_DEFAULT_OPTS="
--height=70%
--layout=reverse
--border
--info=inline
--cycle
"
2
3
4
5
6
7
之后所有普通 fzf 调用都会继承。
这里:
--height=70% 占当前终端高度 70%
--layout=reverse 输入框和结果从顶部开始
--border 显示边框
--info=inline 状态信息内联
--cycle 上下选择可以循环
2
3
4
5
注意:不要为了省事把所有参数都塞到 FZF_DEFAULT_OPTS。
例如 --ansi、--nth、--with-nth 都可能增加解析成本,更适合按具体功能单独使用。
# 9. 为 Ctrl-T 增加文件预览
如果已经安装:
brew install bat eza
可以:
export FZF_CTRL_T_OPTS="
--walker-skip .git,node_modules,dist,build,target
--preview '
if [ -d {} ]; then
eza --tree --level=2 --color=always {}
else
bat --style=numbers --color=always --line-range=:500 {}
fi
'
--bind 'ctrl-/:change-preview-window(down|hidden|)'
"
2
3
4
5
6
7
8
9
10
11
效果是:
- 当前项是目录:右侧显示目录树
- 当前项是文件:右侧使用 bat 显示代码
Ctrl-/:切换 preview 布局或隐藏 preview
# 10. 为 Alt-C 增加目录树预览
export FZF_ALT_C_OPTS="
--walker-skip .git,node_modules,dist,build,target
--preview 'eza --tree --level=2 --color=always {}'
"
2
3
4
以后按:
Alt-C
选择目录时可以同时预览里面的结构。
# 11. Preview:fzf 最重要的高级功能之一
例如:
fd --type f |
fzf --preview 'bat --color=always --style=numbers {}'
2
可以把 fzf 变成一个简单的文件浏览器。
调整 preview 大小:
fd --type f |
fzf \
--preview 'bat --color=always --style=numbers {}' \
--preview-window=right,60%
2
3
4
绑定快捷键切换 preview:
fd --type f |
fzf \
--preview 'bat --color=always {}' \
--bind 'ctrl-/:change-preview-window(right|hidden|)'
2
3
4
# 12. 多选模式
启用:
fzf --multi
或:
fzf -m
常见操作:
Tab 选择当前项目
Shift-Tab 取消/反向移动
Enter 确认
2
3
例如选择多个文件:
fd --type f | fzf -m
这也是用 fzf 编写 Git commit picker、批量文件处理器等工具的基础。
# 13. 文件名安全:不要过度依赖简单命令替换
很多示例会写:
vim $(fzf)
但文件名如果带空格、换行或特殊字符,可能产生问题。
单文件至少推荐:
file=$(fzf)
[[ -n "$file" ]] && code -- "$file"
2
3
需要处理任意文件名、多文件时,可以使用 NUL 分隔:
fd --print0 |
fzf --read0 --print0 --multi |
xargs -0 open
2
3
这里:
--read0
--print0
2
意味着使用 NUL 而不是换行作为记录分隔符。
对于正式脚本,这是更可靠的方案。
# 14. fd + fzf:文件搜索
基础版本:
fd --type f | fzf
带 preview:
fd --type f |
fzf --preview 'bat --color=always --style=numbers {}'
2
搜索目录:
fd --type d | fzf
Finder 打开:
dir=$(fd --type d | fzf)
[[ -n "$dir" ]] && open "$dir"
2
# 15. macOS open + fzf
选择文件后使用默认程序打开:
file=$(fd --type f | fzf)
[[ -n "$file" ]] && open "$file"
2
Finder 中定位文件:
file=$(fd --type f | fzf)
[[ -n "$file" ]] && open -R "$file"
2
open -R 对 macOS 很实用,它不是直接打开文件,而是在 Finder 中定位并选中文件。
# 16. pbcopy + fzf:复制路径
macOS 自带:
pbcopy
可以:
file=$(fd --type f | fzf)
[[ -n "$file" ]] &&
printf '%s' "$file" | pbcopy
2
3
4
甚至直接绑定:
fd --type f |
fzf \
--bind 'ctrl-y:execute-silent(printf %s {} | pbcopy)'
2
3
以后在 fzf 中:
Ctrl-Y
即可把当前路径复制到剪贴板。
# 17. rg + fzf + bat:代码搜索
这是最经典的组合之一。
rg \
--color=always \
--line-number \
--no-heading \
--smart-case \
'Player' |
fzf \
--ansi \
--delimiter=: \
--preview 'bat --color=always --highlight-line {2} {1}'
2
3
4
5
6
7
8
9
10
假设 rg 输出:
src/Player.ts:87:class Player
那么:
{1} = src/Player.ts
{2} = 87
2
所以 preview 可以直接:
bat --highlight-line 87 src/Player.ts
# 18. --ansi:处理带颜色的上游输出
如果上游命令产生 ANSI 颜色,例如:
git log --color=always
fzf 应使用:
fzf --ansi
例如:
git log \
--color=always \
--pretty=format:'%C(yellow)%h%Creset %C(cyan)%ad%Creset %s' \
--date=short |
fzf --ansi
2
3
4
5
不要把 --ansi 无脑加入全局配置;只有输入数据确实带 ANSI 颜色时才需要。
# 19. Git Branch Selector
简单版本:
branch=$(
git branch --format='%(refname:short)' |
fzf
)
[[ -n "$branch" ]] && git switch "$branch"
2
3
4
5
6
增加预览:
branch=$(
git branch --format='%(refname:short)' |
fzf \
--preview 'git log --oneline --graph --decorate --color=always -20 {}'
)
[[ -n "$branch" ]] && git switch "$branch"
2
3
4
5
6
7
这样 fzf 就成了一个非常轻量的 Git branch manager。
# 20. Git Commit Browser
git log \
--color=always \
--pretty=format:'%C(auto)%h %C(cyan)%ad%Creset %s' \
--date=short |
fzf \
--ansi \
--no-sort \
--preview 'git show --color=always {1}'
2
3
4
5
6
7
8
左侧浏览 commit:
a123abc 2026-09-24 Fix adapter
b456def 2026-09-23 Add selector
2
右侧直接查看:
commit 信息
diff
2
这里:
{1}
表示第一字段,即 commit hash。
# 21. 为什么 Git Log 常用 --no-sort
fzf 默认会按照 fuzzy score 重新排序结果。
但 Git 历史原本就是时间序列:
new
↓
old
2
3
因此很多时候使用:
fzf --no-sort
更合理。
同样适用于:
- shell history
- 日志
- 时间顺序事件
- 已经由上游正确排序的数据
# 22. --delimiter、--with-nth、--nth、--accept-nth
这是写高级 fzf CLI 时非常重要的一组参数。
假设输入:
abc123|2026-09-24|rontian|Fix login
def456|2026-09-23|alice|Add UI
2
使用:
fzf \
--delimiter='|' \
--with-nth=2.. \
--accept-nth=1
2
3
4
其中:
--delimiter='|'
定义字段分隔符。
--with-nth=2..
控制用户看到哪些字段。
UI 可以只显示:
2026-09-24 | rontian | Fix login
2026-09-23 | alice | Add UI
2
但最终:
--accept-nth=1
只返回:
abc123
即 commit hash。
这实现了:
内部数据
≠
UI 显示数据
≠
最终输出数据
2
3
4
5
非常适合 Git commit picker。
# 23. --with-nth 和 --nth 的区别
这两个参数很容易搞混。
# --with-nth
控制:
用户看到什么
# --nth
控制:
fzf 搜索哪些字段
假设数据:
hash | date | author | subject
你不希望 hash 出现在 UI,也不希望 hash 参与搜索:
fzf \
--delimiter='|' \
--with-nth=2.. \
--nth=2..
2
3
4
但是原始记录依然存在,在 preview/action 中仍然可以:
{1}
读取 hash。
# 24. fzf 占位符
在 preview、bind、execute 等场景中,经常使用占位符。
常用:
| 占位符 | 含义 |
|---|---|
{} | 当前完整行 |
{1} | 第一字段 |
{2} | 第二字段 |
{2..} | 第二字段直到末尾 |
{+} | 所有已选择项目 |
{q} | 当前查询字符串 |
{n} | 当前结果索引 |
例如:
printf '%s\n' \
'abc|rontian|Fix bug' \
'def|alice|Add UI' |
fzf \
--delimiter='|' \
--preview 'echo hash={1}; echo author={2}; echo message={3}'
2
3
4
5
6
# 25. --bind:fzf 真正进入高级阶段的地方
fzf 支持把按键和事件绑定到 action。
可以理解为:
Key / Event
↓
Action
2
3
例如:
fzf --bind 'ctrl-r:reload(fd)'
按:
Ctrl-R
重新执行 fd,刷新候选列表。
常用 action:
| Action | 用途 |
|---|---|
execute(...) | 执行外部程序,结束后回到 fzf |
execute-silent(...) | 静默执行 |
become(...) | 用新程序替代 fzf 进程 |
reload(...) | 重新加载候选列表 |
change-prompt(...) | 修改 prompt |
change-preview-window(...) | 修改预览布局 |
toggle-preview | 显示/隐藏 preview |
clear-query | 清空搜索词 |
# 26. become():直接进入 Vim / Neovim
传统写法可能是:
fzf | xargs vim
现代 fzf 可以:
fzf --bind 'enter:become(vim {})'
即:
fzf
↓
直接替换成 vim
2
3
结合 rg:
rg \
--line-number \
--no-heading \
--color=always \
'Player' |
fzf \
--ansi \
--delimiter=: \
--preview 'bat --color=always --highlight-line {2} {1}' \
--bind 'enter:become(vim {1} +{2})'
2
3
4
5
6
7
8
9
10
选择:
src/Player.ts:87:...
Enter 后直接进入:
vim src/Player.ts +87
# 27. 动态 reload
fzf 可以不退出界面,动态刷新数据源。
例如进程列表:
ps -Ao pid,user,%cpu,%mem,command |
fzf \
--header-lines=1 \
--bind 'ctrl-r:reload(ps -Ao pid,user,%cpu,%mem,command)'
2
3
4
以后:
Ctrl-R
即可刷新当前进程列表。
这时候 fzf 已经不只是“搜索工具”,而开始接近一个轻量 TUI 框架。
# 28. 动态 rg:输入内容直接驱动代码搜索
大型项目里,可以让 fzf 本身不负责搜索,而把 query 实时交给 ripgrep。
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case"
fzf \
--ansi \
--disabled \
--bind "start:reload:$RG_PREFIX {q} || true" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter=: \
--preview 'bat --color=always --highlight-line {2} {1}'
2
3
4
5
6
7
8
9
工作流程:
输入 PlayerManager
↓
fzf change event
↓
rg PlayerManager
↓
reload
↓
结果返回 fzf UI
2
3
4
5
6
7
8
9
这里:
{q}
表示当前 query。
sleep 0.1 相当于一个简单 debounce,避免每输入一个字符都快速产生大量中间搜索进程。
大型仓库里,这种结构通常比先把整个代码库搜索结果一次性输给 fzf 更合理。
# 29. 进程选择器
浏览进程:
ps -Ao pid,user,%cpu,%mem,command |
fzf --header-lines=1
2
选择 PID:
pid=$(
ps -Ao pid,user,%cpu,%mem,command |
fzf --header-lines=1 |
awk '{print $1}'
)
[[ -n "$pid" ]] && kill "$pid"
2
3
4
5
6
7
默认优先普通:
kill
不要习惯性所有进程都使用:
kill -9
只有普通终止失败时再考虑强制结束。
# 30. 日志流 + fzf
fzf 也可以处理持续产生的数据。
例如:
tail -f server.log |
fzf \
--tail=100000 \
--tac \
--no-sort
2
3
4
5
这里:
--tail=100000
表示只保留最近一定数量的记录,避免无限日志长期占用越来越多内存。
类似地可以:
docker logs -f container_name |
fzf --tail=100000 --tac --no-sort
2
# 31. --tac
--tac 会反转输入顺序。
例如历史记录:
history |
fzf --tac --no-sort
2
让较新的内容更靠近当前操作位置。
对于:
- history
- logs
- event stream
都很有用。
# 32. 推荐的 macOS fzf 配置
如果使用现代 shell integration,一份比较实用的配置可以是:
# ------------------------------------------------------------
# fzf
# ------------------------------------------------------------
export FZF_DEFAULT_OPTS="
--height=70%
--layout=reverse
--border
--info=inline
--cycle
"
export FZF_CTRL_T_OPTS="
--walker-skip .git,node_modules,dist,build,target
--preview '
if [ -d {} ]; then
eza --tree --level=2 --color=always {}
else
bat --style=numbers --color=always --line-range=:500 {}
fi
'
--bind 'ctrl-/:change-preview-window(down|hidden|)'
"
export FZF_ALT_C_OPTS="
--walker-skip .git,node_modules,dist,build,target
--preview 'eza --tree --level=2 --color=always {}'
"
source <(fzf --zsh)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
如果当前仍然使用旧安装方式:
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
则保留这一行即可,不要再重复 source <(fzf --zsh)。
上面的:
FZF_DEFAULT_OPTS
FZF_CTRL_T_OPTS
FZF_ALT_C_OPTS
2
3
仍然可以放在加载 ~/.fzf.zsh 之前。
# 33. 推荐的日常使用组合
如果只想掌握最值得使用的一部分,我建议按这个顺序:
Ctrl-T
Ctrl-R
Alt-C
↓
**<Tab>
↓
--preview
↓
--multi
↓
fd + fzf
rg + fzf + bat
Git + fzf
↓
--delimiter
--with-nth
--nth
--accept-nth
↓
--ansi
↓
--bind
↓
execute
execute-silent
become
reload
↓
{1} {2} {q} {+}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
掌握这部分以后,fzf 已经足够覆盖绝大多数日常 CLI 交互场景。
# 34. 最重要的设计思维
不要把 fzf 理解成:
fzf = 文件模糊搜索器
更好的模型是:
Data Source
│
┌─────────┼─────────┐
│ │ │
git rg fd
│ │ │
└─────────┴─────────┘
↓
Normalize
↓
fzf
┌────────┼────────┐
Query Preview Multi
│ │ │
└────────┼─────────┘
↓
Action
┌────────┼─────────┐
output execute become
│
reload
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fzf 最适合负责的是:
交互 UI 和选择。
真正的业务逻辑应该留给:
git
rg
fd
awk
sed
脚本
CLI 程序
2
3
4
5
6
7
例如一个 Git commit picker,推荐结构是:
Git 提供 commit 数据
↓
脚本整理字段
↓
fzf 显示 / 搜索 / 多选 / preview
↓
脚本取得真实 commit hash
↓
执行 cherry-pick
↓
脚本处理冲突状态机
2
3
4
5
6
7
8
9
10
11
不要让业务逻辑依赖 fzf 中为了好看而渲染出来的字符串。
尤其在有 ANSI 颜色、作者配色、列对齐和多字段显示时,应该尽量区分:
真实数据
显示数据
搜索数据
返回数据
2
3
4
这正是:
--delimiter
--with-nth
--nth
--accept-nth
2
3
4
这些参数真正的价值。
# 35. 总结
在 macOS 上,fzf 最值得配置的并不是单独执行:
fzf
而是把它融入整个 CLI 工作流:
zsh
+
fzf
+
fd
+
rg
+
bat
+
eza
+
Git
+
pbcopy
+
open
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最终你会得到一套非常统一的交互方式:
文件 → fzf
目录 → fzf
历史命令 → fzf
Git branch → fzf
Git commit → fzf
代码搜索 → fzf
进程 → fzf
日志 → fzf
自定义 CLI → fzf
2
3
4
5
6
7
8
9
而且不需要引入一个复杂的重量级 TUI 框架。
对于经常使用终端的 macOS 开发者来说,fzf 真正的价值并不是“搜索得快”,而是:
给任意 CLI 数据源快速增加一个高效、统一、可组合的交互层。
# 参考资料
- fzf 官方仓库:https://github.com/junegunn/fzf
- fzf README:https://github.com/junegunn/fzf/blob/master/README.md
- fzf Advanced Guide:https://github.com/junegunn/fzf/blob/master/ADVANCED.md
- Homebrew fzf:https://formulae.brew.sh/formula/fzf
沪公网安备31011502401077号