
上周帮人找一份 2019 年的合同扫描件,文件名记不清,只记得里面有「补充」两个字。他在资源管理器里翻了二十分钟,我按了四下键盘,两秒出结果。差别不在手速,在你到底在「遍历」还是在「查表」。
本地找文件这件事,工具其实只有四种基本形态,剩下的都是这四种的变体。它们的区别不是谁更快,而是各自认哪一种「问题」。选错的典型症状,是用一个遍历型工具去回答一个索引型问题。
1. Everything:读文件表,不读文件
Everything 在 Windows 上启动后几秒就能索引几十万个文件,因为它根本不遍历目录,而是直接读 NTFS 的主文件表(MFT)。这是唯一一个「文件越多、优势越大」的工具,因为它的索引成本几乎和文件数量无关。
代价是它只在 NTFS 卷上有效。FAT32 的 U 盘、exFAT 的移动硬盘、网络映射盘,都得退回扫描模式,速度立刻掉到和资源管理器一个量级。它快,是因为它偷看的是操作系统的账本,而不是因为算法更聪明。
它按文件名匹配,支持通配符和正则,不支持内容搜索。想找「文件内容里含某句话」,它帮不上忙。把它当全文搜索用,是所有误用里最常见的一种。
2. fd:按名字找,默认懂规矩
fd 是 find 的现代替代品,Rust 写的,默认并行遍历。它真正改变体验的不是速度,是默认行为:自动跳过隐藏文件、自动读取 .gitignore、自动跳过 node_modules。find 需要你写一堆 -not -path 才能达到的状态,fd 是默认值。
这个默认值在仓库里是优点,在仓库外是陷阱。你想找的东西刚好在 .gitignore 里,fd 就当作它不存在,不会给任何提示。它的「安静」既是设计,也是它最容易骗人的地方。
fd -e pdf -x ls -lh {} \; # 找所有 pdf 并列出大小
fd --hidden --no-ignore 'config' # 连隐藏和被忽略的一起找
fd -t d -d 1 # 只看一层目录
3. ripgrep:找内容,顺手能列文件
ripgrep 是为「在一堆文件里找一段文本」设计的。它同样尊重 .gitignore,默认跳过二进制文件,多线程扫描,输出带行号和上下文。它比 grep 快一个数量级的原因很简单:它先决定哪些文件不用读。
它有一个常被忽略的能力:rg --files 直接列出所有会被搜索的文件,速度比 fd 还快一点。于是很多人拿它当文件名搜索用,能用,但这是副产品不是主功能。它在设计上并不处理「文件名匹配」这件事,只处理「哪些文件值得读」。
rg -n --hidden -g '!*.min.js' '发票号' .
rg -l -t py 'requests\.get' | head
rg --files -g '*.csv' | wc -l
4. fzf:不做搜索,做筛选
fzf 没有索引,也不扫描磁盘。它从标准输入读一批候选,实时做模糊匹配,你把结果敲窄到一行。它是这四件工具里唯一一个「人机来回」的,其他三个都是「一次问,一次答」。
单独用它没有意义,它的价值在管道里。fd | fzf 是文件名筛选,rg --files | fzf 是全库筛选,git ls-files | fzf 是只在自己改过的文件里筛。它把「精确表达」换成了「边看边缩」,适合你说不清要什么的时候。
四维对照:它们不是同一类东西
把四个工具放在同一张表里,能看清一件事:它们的「快」来自完全不同的机制,所以比较速度没有意义。该比较的是「在什么条件下它会失效」。
| 工具 | 索引方式 | 搜索对象 | 中文/特殊字符 | 失效条件 |
|---|---|---|---|---|
| Everything | 读 NTFS 主文件表,常驻服务 | 文件名、路径 | 需要 UTF-8 编码设置,否则乱码 | 非 NTFS 卷、网络盘、刚写入未刷新 |
| fd | 无索引,每次并行遍历 | 文件名、路径 | 原生 UTF-8,无问题 | 目录树极深或文件数百万时变慢 |
| ripgrep | 无索引,边遍历边读 | 文件内容 | 需注意编码,GBK 文件要转码 | 大文件、二进制、被 .gitignore 排除 |
| fzf | 无索引,读管道 | 候选列表 | 依赖终端字体与输入法 | 候选超过百万行时交互变卡 |
中文那一列值得单独说。Everything 在中文 Windows 上默认按系统编码处理,遇到跨语言文件名会出现「搜得到但显示乱码」或「显示正常但搜不到」。这类问题不是 bug,是编码在两套系统之间没有对齐。
三套组合,和三个「选错了」的信号
第一套,Windows 办公机:Everything 常驻,负责「我知道名字里有什么」,配合 es.exe 命令行版接进批处理;找内容时用 ripgrep 指定目录扫一遍。这套的关键是别让 Everything 去搜内容,它一搜内容就退化成遍历。
第二套,代码仓库:fd 找文件,rg 找代码,fzf 接管交互。装 fzf 的 shell 集成后,Ctrl-T 是「在当前目录递归选文件」,Alt-C 是「选目录并跳进去」。这两个快捷键能顶掉一半的资源管理器操作。
第三套,没有索引的服务器:只用 rg 一件。rg --files | fzf 当文件浏览器,rg -n '关键词' 当内容搜索。在别人的机器上,装一个静态二进制比装三个工具现实。
如果上面三套你都不想装,还有一条更简单的判断:先问自己记不记得文件名里的一半。记得,就用索引;只记得内容,就用扫描。工具的分工,本来就是把「你记得什么」翻译成「该查哪里」。
最后是三个反向信号,它们不告诉你该装什么,只告诉你已经装错了。工具用得别扭,通常不是手速问题,是把问题交给了不认这类问题的工具。
信号一:你在 Everything 里配置「索引目录」。这说明你的盘不是 NTFS,或者你在 macOS 上找替代品。Everything 没有「索引哪些目录」这个概念,出现这个念头就说明工具选错了。它的模型是「整个卷」,不是「某些文件夹」。
信号二:你用 ripgrep 拼文件名正则,还加了 --files 和一堆 -g。这时候你要的是 fd,或者 Everything。工具的边界写在它的默认行为里,不在它的参数列表里。
信号三:你希望 fzf「建个索引,下次快一点」。fzf 的每次启动都是全量读入,它靠的是候选数量不大。想让 fzf 变快,正确做法是在管道前面把候选筛小,而不是给它加索引。
写在最后
这四件工具真正的分野,是「谁负责缩小范围」。Everything 靠文件表缩小,fd 靠忽略规则缩小,rg 靠文件类型缩小,fzf 靠你的眼睛缩小。四种缩小方式对应四种你对自己需求的确定程度。
确定名字,就用索引;确定模式,就用遍历;确定内容,就用扫描;什么都不确定,就用交互。找文件慢,多数时候不是工具不够快,是你还没想清楚自己记得的是哪一部分。
那天最后找到那份合同,靠的不是任何工具,是他突然想起文件名叫「附件三」。工具只负责在你想起之后,把两秒还给你。它们不解决记忆问题,只解决记忆之后的检索问题。
你电脑里最难找的那份文件,最后是靠什么线索找到的——文件名、内容,还是翻聊天记录?

评论 (0)