用久了 AI cli 发现它们大都在检索文件/代码时使用 rg
于是自己去翻了一下 rg 仓库, 并把它与 grep 做了下对比

TL; DR

rg 的优势在于场景下的 搜索速度 和 准确度。它对于git仓库有 特性优化,对 coding agent 来说,少搜无关内容、少产生噪声,往往比单纯快几毫秒更有价值。

正文

rg 全名 ripgrep
命名有点意思, 让 grep 直接下岗(R.I.P.)吗? 😂

那么,rg 和 grep 有什么区别,为什么看 coding agent 干活,调的全是 rg?

默认行为:搜哪些文件

我在本机准备了一个只有几个文件的小仓库:src/a.rs、src/b.py、build/out.js 和 .hidden/note.txt。.gitignore 排除了 build/ 和 .hidden/,文件里都包含 TODO。

在仓库根目录执行 rg TODO,默认只会显示未被忽略的代码文件,例如:

src/a.rs:fn main() { /* TODO: fix */ println!("hello TODO"); }

而 grep -r TODO . 会继续进入 build/、.hidden/,甚至 .git/,把里面的匹配也列出来。grep 并不知道 .gitignore 代表什么;它只是递归搜索指定目录。

若想让 grep 在这个仓库里避开常见的生成目录和依赖目录,就得自己补上排除规则,例如:

grep -rn TODO . \
  --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=build \
  --exclude-dir=dist --exclude-dir=.venv --exclude-dir=__pycache__ \
  --exclude='*.min.js' --exclude='*.map'

仓库越复杂,这些规则就越容易变成长长的一串。rg 默认遵循 .gitignore,也会跳过隐藏文件和二进制文件;这让它在代码仓库里的默认搜索范围通常更接近开发者的预期。

另外,两者还有两个常见差异:

  • 大小写:两者默认都区分大小写。rg -S 启用智能大小写:模式全小写时忽略大小写,含大写时仍严格匹配。例如 rg -S todo 可以匹配 Todo、todo 和 TODO。
  • 行号:rg 在终端输出时通常显示行号;输出被管道接走时,默认可能省略行号。需要稳定输出行号时,显式加 -n。grep 需要通过 -n 请求行号。

性能:快多少,差在哪里

我用两类仓库做了对比。每次先预热,再运行三次并取最短时间。环境是 arm64、2 核,ripgrep 14.1.1 和 GNU grep 3.11。以下数字是本机结果,不代表所有机器或仓库。

干净仓库:Django,7087 个文件,约 38 MB 源码

查询rggreprg 用时优势
字面量 TODO0.063s0.116s1.8 倍
正则 def\s+\w+\(self0.091s0.197s2.2 倍
-w save(整词)0.070s0.158s2.3 倍
连续搜索 20 次1.55s2.91s1.9 倍

在这组数据里,两者搜索的文件范围接近,rg 大约快两倍。差距主要反映了实现和并行策略等因素,不能简单归因于单一原因。

包含 node_modules 的本机仓库,约 5 万个文件

指标rggrep -r
耗时0.094s1.735s(约 18.4 倍)
结果行数2072504
输出字节数25,62617,375,526
命中文件位置全部在源码目录64% 在 node_modules

这组结果里,决定体验的首先是搜索范围:grep 搜出了大量依赖代码,输出约 17 MB;rg 的输出只有约 25 KB。对 agent 来说,搜索结果还要进入上下文窗口,输出太多会被截断,也会挤掉真正有用的信息。

同时,我也测了一个反例:列文件时,rg --files 用时 0.043s,find . -type f 用时 0.036s,find 略快。rg 并不是所有任务都更快,它的优势集中在代码仓库搜索及其默认过滤行为上。

需要留意,这台机器只有 2 核,而 rg 默认会并行搜索;不同硬件和数据集会改变结果。对于单个大文件,或使用 C locale 的简单搜索,grep 也可能表现得很好。

搜索引擎和表达式语法

rg 默认使用 Rust 的正则表达式引擎。它通过字面量优化、有限自动机和 SIMD 等技术提高搜索效率;目录遍历则由 ignore 等组件处理,并可以并行执行。

有限自动机也带来一个取舍:默认引擎可以保证正则搜索的线性时间,但不支持反向引用和环视等需要回溯的语法。比如:

$ rg '(a)\1' br.txt
rg: regex parse error:
    (?:(a)\1)
          ^^ error: backreferences are not supported
Consider enabling PCRE2 with the --pcre2 flag

遇到这类语法,可以用 -P 切换到 PCRE2,例如 rg -P 'a(?=b)'。

GNU grep 提供 BRE、ERE 和 PCRE 三种模式,分别通过 -G、-E 和 -P 选择。不同模式的转义规则不完全相同:例如反向引用在 BRE 中写作 \(a\)\1,GNU grep 的 ERE 扩展中则可写作 (a)\1。脚本的匹配和性能也可能受 locale 影响;设置 LC_ALL=C 与使用 UTF-8 locale 时,表现可能不同。

rg 的默认语法和 Unicode 行为则更一致。例如 rg -w nai 不会把 naïve 中的 naï 当成一个完整单词来匹配。若需要 grep 的 PCRE 能力,rg -P 仍然可用。

所以,为什么 agent 常用 rg

速度是一部分原因,默认搜索范围和输出形式同样重要。

首先,主流 coding agent 往往会随产品提供 rg,或把它列为依赖。其次,一次代码任务通常要反复查定义、调用点、测试和配置;忽略文件和依赖目录能减少重复搜索产生的噪声。

输出也比较适合工具消费。rg 会按需输出文件路径和行号,--vimgrep 可包含列号,--json 则提供结构化结果,其中有匹配行、行号、偏移量和子匹配信息。相比让模型从大量无关文本里筛选,规整、聚焦的结果更容易继续处理。

.gitignore 还有一层实际意义:它反映了项目维护者认为哪些内容不属于日常源码搜索范围。rg 默认沿用这套约定,agent 因而不必每次重新遍历依赖、构建产物和缓存目录。

什么时候用 grep

grep 仍然有它更合适的场景:

  • 可移植性:POSIX 环境提供 grep。最小化 CI 镜像、BusyBox 和老脚本通常可以直接使用它;rg 不承诺 POSIX 兼容。
  • 不能安装额外工具的机器:生产环境或受限容器里,grep 可能已经是唯一可用的选择。
  • 依赖既有 BRE/ERE 行为的脚本:保留原命令通常比迁移表达式更省事。
  • 管道过滤:例如 cat log | grep -v DEBUG。rg 也能读标准输入,但这里用 grep 简单直接。
  • 单文件、纯文本、简单模式:这时递归遍历和 ignore 规则都派不上用场,两者差距通常不大。

反过来,也要记得 rg 的默认过滤可能让某些文件“消失”。要搜被忽略的文件,可用 --no-ignore;要纳入隐藏文件,可用 --hidden。rg -uuu 会同时关闭 ignore、隐藏文件和二进制过滤,搜索范围会更接近 grep -r。因此,遇到“明明文件在,却搜不到”时,先检查这些默认规则。

常用命令对照

目的greprg
递归搜索grep -rn pat .rg -n pat
只列文件名grep -rl pat .rg -l pat
计数grep -rc pat .rg -c pat
忽略大小写 / 智能大小写grep -irg -i / rg -S
整词 / 固定字符串grep -w / grep -Frg -w / rg -F
只搜某种语言--include='*.py'rg -t py(排除用 -T py)
排除文件--exclude='*.min.js'rg -g '!*.min.js'
排除目录--exclude-dir=distrg -g '!dist/'
显示上下文-A/-B/-C-A/-B/-C
多行匹配grep -Pzo(输出处理较麻烦)rg -U
显示匹配位置无直接对应选项rg --vimgrep / rg --json
替换匹配文本配合 sed 等工具rg --replace(只改输出,不修改文件)
搜压缩文件通常先解压rg -z
指定文本编码通常先转码rg -E gbk
搜索统计无直接对应选项rg --stats
列出文件find . -type frg --files
反向引用 / 环视grep -P 或 BRE/ERE 方言rg -P

rg 也支持通过 RIPGREP_CONFIG_PATH 指定配置文件,把常用选项固定下来。GNU grep 曾经支持的 GREP_OPTIONS 已被废弃,不建议依赖它。

自己动手复现

文中的数据来自本机。你可以先确认版本和搜索范围,再按相同条件计时:

rg --version
rg --stats -n 'PATTERN' .
rg --files | wc -l
find . -type f | wc -l

# 先预热,再重复运行并比较多次结果
time rg -n --no-heading 'TODO' .
time grep -rn 'TODO' .

最后回到开头的问题:装 rg 不一定会让每一次搜索都快十几倍。它在代码仓库里的价值,是默认把搜索范围收在更有用的地方。速度是加分项,少看无关结果才是 agent 最直接的收益。


参考:ripgrep README 与 FAQ、GNU grep 手册,以及互联网上 Coding Agent 的相关信息。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐