ripgrep 常常被介绍成一个很快的 grep 替代品,这个说法方向没有错,却薄了一层。它会让项目看上去像是把全部赌注压在一个特别快的 matcher 上。Andrew Gallant 在 2025 年那场维护者访谈里给出的线索更有用,因为他的讲法很快就把视线往外推开了。等他开始逐个说起那些因为 ripgrep 才被自己造出来的 crates,这个项目就不再只是一个 regex 故事,而更像一条在多个关口上都做了取舍的 search pipeline。[1]

官方文档把这条线索写得很扎实。README 的重心并没有停在 regex search,还把 recursive search、.gitignore 处理、hidden file filtering、binary file filtering、Unicode support,以及按需切到 PCRE2 的能力并列摆出。[2][4] Guide 继续把默认行为落成操作顺序:ripgrep 在目录里执行时,先判定哪些路径不进入搜索范围,再把预算交给匹配阶段。[3] 放到真实仓库里看,第一份性能收益来自候选集的收缩,无关文件在入口处就被排除在外。

顺着访谈与文档一起看,ripgrep 更像一条以 filesystem 为起点的 search pipeline。regex engine 当然重要,但它是在语料已经被 ignore-aware walker 缩窄之后、在大批 ignore 规则已经被压缩成便宜匹配判断之后,才进入主舞台。[1][2][3][5] 也正因为这样,ripgrep 的速度感常常在最复杂的匹配工作开始之前就已经出现了。

配图说明:题图使用 Andrew Gallant 的真实 GitHub 头像肖像。这张图合适,因为本文围绕的是维护者本人对工具内部边界的解释,重心落在设计决策,而不在装饰性的终端截图或泛化的键盘照片。[6]

大约从 8:12 开始,Gallant 把文件发现本身讲成产品的一部分

这支访谈最有用的段落,出现在 Gallant 从单个 binary 的叙述转向 supporting crates 的命名时刻。[1] 大约在 8 分钟左右,他把 ignore crate 解释成一条具备 multi-threading 与 gitignore support 的 walker,并明确说这就是 ripgrep 找文件的驱动力。[1] 这句话把边界说得非常清楚:walker 直接构成搜索流程本体。

Guide 里 automatic filtering 那一节,把同一件事换成用户视角说得很完整。默认情况下,ripgrep 会忽略 .gitignore.ignore.rgignore 命中的文件,跳过 hidden files 和 directories,跳过 binary files,也不会跟随 symlinks,除非用户显式放开这些边界。[3] 换个说法,ripgrep 在真正报告 match 之前,已经花了很多工程精力去判断哪些路径根本不值得进入搜索预算。这个姿态和那种“先把所有文件递归打开,再让 matcher 解决问题”的朴素模型完全不同。

也正因为这样,ripgrep 的优势往往在大型代码仓库里更明显,而不只是在单文件基准里显得好看。这个项目先对 relevance 有立场,再对 matching 有立场。速度先来自语料变小。

大约从 8:52 开始,globset 把巨大的 ignore 列表压成一个 matcher

下一步真正关键的动作,是 Gallant 把 globset 说成一个能把许多 globs 编译进同一匹配对象里的库,并举例说到,一个仓库里成千上万条 ignore 规则可以被压进一套 finite-state-machine 风格的结构里。[1] 这段很容易被当成实现细节带过去,实际上它解释了 ripgrep 为什么能让默认 filtering 既快又重要。严肃仓库里的 ignore handling 从来并非一个小装饰。它会意味着拿成千上万条 pattern 去对照大量路径节点。这个环节一旦笨重,“smart defaults” 就会反过来变成性能负担。

README 和 Guide 在这一点上是互相咬合的。README 写得很直白:ripgrep 用 RegexSet 来处理 ignore patterns,让单一路径可以同时对照很多模式。[2] Guide 又把优先级关系排清楚:.rgignore 覆盖 .ignore.ignore 覆盖 .gitignore,连续追加的 -u 则会一层层把这些保护拆开。[3] 这组设计把 ignore 语义变成一项第一层的 correctness 问题。工具不只是要快,还得在 inclusion / exclusion 的判断上,和用户的仓库规则、局部覆盖文件保持同样的语义预期。

Gallant 在访谈后段也承认,想把 ignore semantics 做到完全正确一直会冒出顽固 bug,ignore crate 本身也很难轻易重写。[1] 这层坦白很有价值,因为它把真正复杂的地方露了出来。ripgrep 的困难并不只在 regex engine,本质上还有一条同样难的契约:把该搜的文件搜进去,把不该搜的文件稳稳挡在外面。

大约从 10:39 开始,regex engine 变成一段被清楚限制的舞台

访谈再往后,话题转回 matcher 本身,Gallant 开始谈自己那些 regex 和 I/O crates 在别处的复用。[1] 文档把这层边界写得更清楚。README 说 ripgrep 的速度,一部分来自默认 engine 里 finite automata、SIMD 和 aggressive literal optimizations,另一部分来自它会按工作负载在 memory map 与 buffered incremental search 之间选路。[2] FAQ 则把边界补齐:默认 engine 不支持 lookaround 和 backreferences,因为它的中心目标是用 finite state machines 保住线性最坏情况;如果用户真需要这些特性,再用 -P 或自动切换的 engine 模式把 PCRE2 带进来。[4]

这份设计的价值,在于把 cleverness 放进受控边界里。ripgrep 给出的承诺是一条可预测的默认路径,让普通 code search 稳定运行,同时为确实需要 fancy regex 的场景保留一条明确的切换通道。[2][4] 放在工程语境里看,昂贵情况被显式化之后,最常见工作负载的性能边界才有机会长期稳定。

README 里的 benchmark 讨论也在重复同一条主线。文档多次强调 literal optimizations 会决定结果,而当 pattern 缺少足够的 literal footholds 时,性能 cliff 会出现。[2][5] 更准确的心智模型可以写成三步:先利用 literal,默认停留在受约束的 automata,遇到更高表达需求时再切换 engine。matcher 之所以显得快,是因为项目持续先问:眼前这一份工作到底属于哪一类。

大约从 13:27 开始,-u 阶梯把 ripgrep 的用户契约公开出来

访谈末段回到了许多日常用户熟悉、许多新用户却容易踩空的那件事:ripgrep 默认会忽略很多东西,而 Gallant 也明确把 -u 这一串层级说成了逐步放宽搜索面的方式。[1] 这和 Guide 完整对上:一个 -u 会拿掉 ignore-file filtering,-uu 会把 hidden files 带进来,-uuu 再把 binary files 带进来。[3] 这不只是一个好记的 shortcut,它其实就是 ripgrep 整体哲学的公开接口。

带着这种契约的工具,实际在告诉用户:relevance 是默认值,exhaustiveness 是显式放宽之后才会到来的状态。也正因为这样,ripgrep 和那类“先全搜再说”的老工作流会有很明显的体感差异。它默认相信,大多数 code search 都希望仓库自己定义的 disposable output、hidden state、binary junk 留在外面,除非使用者主动把光圈拧大。[2][3] 记住这一层,默认行为会显得锋利;忘掉这一层,工具就容易被误读成“为什么少搜了东西”。

把这支视频收回来,最值得带走的判断很实:将 ripgrep 视作一台超快 regex engine 会遗漏它真正的工程形态。更贴切的说法,是它把 search pipeline 分成三段边界清楚的工作:先用理解 ignore 语义的 walking 缩小 filesystem,再用编译好的 glob/filtering 结构做低成本判断,最后让默认 matcher 在线性边界里完成大多数匹配,直到操作者明确要求更强表达力。[1][2][3][4][5] 终端里那种“很快”的体感,正是这些边界共同成立后的可见结果。

来源

  1. Canonical Ubuntu,《Meet the Maintainers: the mind behind ripgrep - Andrew Gallant's blazing-fast search tool》,YouTube 视频,发布于 2025 年 12 月 16 日。
  2. BurntSushi,ripgrep README——默认 filtering 行为、Unicode support、PCRE2 切换、benchmark 说明、RegexSet 与 lock-free parallel directory iteration。
  3. BurntSushi,ripgrep User Guide——recursive search、automatic filtering、ignore-file 优先级,以及 -u / -uu / -uuu 的逐层放宽模型。
  4. BurntSushi,ripgrep FAQ——parallelism 带来的非确定性顺序,以及默认 finite-automata engine 与可选 PCRE2 support 之间的边界。
  5. Andrew Gallant,《ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}》——benchmark 方法与 literal optimizations 在实际性能中的作用。
  6. Andrew Gallant(BurntSushi)GitHub 个人页——本文配图所用肖像的来源页面。