工具与库 · 2026 年 11 月 11 日

ripgrep + fd:让命令行搜索快一个数量级

在几十万文件的仓库里 grep -r,一半时间花在 node_modules 和二进制文件上。ripgrep 和 fd 快的原因不神秘:默认少搜、并行搜、跳过没意义的东西。

在一个前端加上后端的 monorepo 里找一段日志文案,grep -r "timeout" . 跑了四十秒,输出还混着一堆 node_modules 里的压缩文件和二进制乱码。最后是在 IDE 里点开的。

ripgrep(命令名 rg)和 fd 干的就是这件事:在你还没开始优化之前,先把「不该搜的东西」排除掉。它们的快有一半来自这里,不是玄学。

快在什么地方#

先把原因说清楚,才不会对速度有不切实际的期待。

  • 默认遵守忽略规则。仓库里的 .gitignore、.ignore、.rgignore 会被读取,node_modules、dist、build 这类目录自动跳过。grep -r 不会读这些。
  • 默认跳过二进制和隐藏文件。命中二进制时 grep 会打印一行 Binary file ... matches,rg 直接不搜。
  • 并行。多核机器上按文件分片,fd 遍历目录时同样是并行的。
  • 正则引擎。Rust 的正则库对字面量做预筛,先用极快的子串匹配定位候选,再做完整匹配。这也是它不支持反向引用和环视的原因(后面会讲)。

所以「快一个数量级」是有条件的:在有明确忽略规则的代码仓库里,差距最大;在全盘或纯文本大目录里,差距会缩到两三倍。具体数字要自己量。

还有一点常被忽略:grep 是单线程的,在大目录上串行地打开、读取、关闭每个文件。文件数越多,系统调用的固定开销越明显,并行的收益也越大。

两者都有各平台的二进制发布,主流包管理器基本都能装。装完用 rg --version 确认,具体安装方式以官方文档为准。有个小坑:部分发行版把 fd 的可执行文件命名成 fdfind,脚本里别硬编码命令名。

常用命令与工具差异#

bash
# 找 TODO,跳过忽略目录,带行号
rg -n --hidden -g '!node_modules' '\bTODO\b' src/

# 只列文件名,按语言类型过滤
rg -l --type py 'deprecated'

# 前后各三行上下文,顺带输出统计
rg -C 3 'panic' --stats

# 用 json 输出接 jq,方便写脚本
rg --json 'unsafe' src/ | jq -r 'select(.type=="match") | .data.path.text'

fd 负责找文件,语法比 find 短得多:

bash
fd -e md -x wc -l            # 每个 Markdown 文件的行数
fd -H -t f '^\.env'          # 包含隐藏文件,只要普通文件
fd -e ts src/ -x echo {}     # 先打印,确认范围再执行

-x(--exec)对每个结果执行命令。它很方便,也很危险,尤其是配合 sed -i 或 rm 时。习惯是先 echo 预览一遍,确认无误再换成真正的命令。

忽略规则比参数更重要#

rg 的效果取决于它跳过了什么。三层规则按优先级:命令行 -g 和 --type 最直接,其次是目录里的 .rgignore、.ignore,最后是 .gitignore。

实际项目里值得做两件事。一是在仓库根放一个 .ignore,把生成目录(dist/、coverage/、vendor/)写进去,这样连没装 git 的环境也能生效。二是用 --type-add 扩展类型定义,把 .vue 之类的后缀归到自定义类型,之后 --type vue 就能用。

反过来,搜不到东西时按顺序检查:是不是被 -g '!...' 排除了、是不是隐藏文件、是不是被判成二进制。rg --debug 会打印它读了哪些忽略文件,比猜快得多。

和 grep / find 的差异#

需求 grep / find rg / fd
读取忽略规则 不支持,手动 --exclude 默认读 .gitignore 等
二进制文件 会打印提示行 默认跳过
多线程 无 默认启用
正则语法 BRE/ERE Rust regex,无回溯
反向引用 / 环视 部分支持 默认不支持,需 -P 切 PCRE2
内容替换 不支持 不支持,配 sed 或用 sd
查找文件 find 表达式 fd 模式 + 类型 + 执行
结构化输出 纯文本 --json

两个常见误解:rg 不能替换内容(它只负责找),fd 也不是「更快的 find」这么简单——它的默认值更贴近日常使用,比如智能大小写、默认忽略隐藏文件。

自己量一次,别信倍数#

比较必须在公平条件下做,否则量的是「忽略规则」而不是「引擎速度」。

bash
# 公平比较:同一目录,同样的文件范围
time grep -rn --include='*.py' 'deprecated' src/ > /dev/null
time rg -n 'deprecated' --type py src/ > /dev/null

# 让 rg 不忽略任何东西,贴近 grep 的默认行为
time rg -uu -n 'deprecated' src/ > /dev/null

三点要注意:

  1. 多跑几次。第一次是冷缓存,磁盘还没热,数字会偏高。取三次的中位数。
  2. 区分两种快。rg 不加参数时快,一部分原因是少搜。-uu 之后两者搜的文件集才接近,差距反映的是并行和引擎。
  3. 看真实目录。拿一个玩具目录测出来的数字没有参考价值,用你天天打开的那个仓库。

顺带一提:在 git 仓库里,git grep 通常也比 grep -r 快,因为它只搜被跟踪的文件。选择顺序可以是 rg → git grep → grep,看你的场景需要哪个。

边界与日常习惯#

  • 需要 PCRE 特性。反向引用、环视这些 Rust regex 不支持,加 -P 切到 PCRE2 引擎,或者回退到 grep -P。
  • 脚本要可移植。CI 镜像里不一定装了 rg。写给别人用的 shell 脚本,grep 更保险;自己日常用,rg 更舒服。
  • 要跟随符号链接。rg 和 fd 默认不跟随,需要显式 -L,否则会漏文件而且毫无提示。
  • 编码问题。Windows 上的 UTF-16 文件、GBK 文本,rg 可能直接当二进制跳过。加 -a 强制按文本处理,但要接受乱码风险。
  • 超大单文件或流。tail -f app.log | rg pattern 是常见用法,但别对几个 G 的文件做全量正则回溯。
  • 批量修改。fd -x 配合 sed -i 出错时没有撤销。生产目录里先备份,或者用 git status 兜底。

还有一个容易踩的:忽略规则的优先级。.rgignore 会覆盖 .gitignore,--no-ignore 又全部关掉。搜索「明明存在却搜不到」时,先用 rg --files -uu | head 看看文件到底在不在结果集里。

把这些固化成习惯#

工具的价值在于变成肌肉记忆。几个值得背下来的组合:

  • rg -l pattern | xargs -r 命令:找到文件再批量处理,比 -x 更好控制。
  • fd -e py -x ruff check {}:只对某类文件跑检查,范围可控,也方便配合 --changed-within 限定最近改动的文件。
  • rg --files | wc -l:快速知道仓库有多少文件,判断要不要加忽略规则。
  • rg -c pattern:按文件统计命中行数,接一个 sort 就能看出热点在哪个目录。

结论#

  • rg 和 fd 的快,一半来自默认忽略,一半来自并行和引擎,先理解这一点再用。
  • 日常搜索直接上 rg,找文件用 fd,别背 find 的表达式。
  • 比速度要在同一目录、同一忽略策略下量,并且区分「少搜」和「搜得快」。
  • 需要反向引用、可移植脚本、跟随软链接时,老老实实回退到 grep / find。
  • fd -x 前先 echo 预览,这条能省掉一次误删。

完

#ripgrep#fd#命令行#效率工具
回到顶部 ↑