ripgrep 命令在 Linux 中的快速递归搜索

使用 grep 在大型代码库或满是日志文件的目录中搜索时,往往要加上 -r 递归、-n 显示行号,以及 --include 或 --exclude 规则来排除无关文件。在 Git 仓库中,你可能还需要为构建目录、vendor 文件和二进制输出额外设置排除项。

ripgrep(rg)以更合理的默认设置处理常见场景。它会递归搜索目录、遵循 .gitignore、.ignore 和 .rgignore 规则、在递归搜索时跳过隐藏文件和二进制文件,并输出友好的交互式结果。由于使用了 Rust 的正则引擎和并行的目录遍历,它在许多大型文件树上也比传统的递归 grep 快得多。

本指南讲解如何在 Linux 中使用 ripgrep 命令,涵盖从基础搜索到文件类型过滤、上下文行、替换预览以及配置默认值。

什么是 rg 命令

rg 命令是 ripgrep 的可执行文件名,后者是一款面向行的搜索工具,用 Rust 编写,由 Andrew Gallant 于 2016 年首次发布。当人们提到 rg 命令时,指的就是 ripgrep;之所以取这个短名,是因为该工具本就设计得需要频繁键入。

与 grep 的实际差异在于默认行为。一条普通的 grep "error" 会读取标准输入,需要加上文件名才能搜索文件。它必须加上 -r 才能进入目录递归,-n 才能为行编号,还要用 --exclude-dir 规则才能跳过诸如 node_modules 或 .git 之类的目录。

在交互式终端中,rg "error" 会从当前目录递归搜索、显示行号与颜色、跳过二进制文件,并遵循 Git 仓库中的 .gitignore 规则。如果你把输入通过管道传给 rg,它会转而搜索这些输入。当你管道输出或重定向其输出时,行号与颜色默认是关闭的。

Behaviorgreprg
递归搜索Needs -rDefault
终端输出中的行号Needs -nDefault
遵循 .gitignoreNoDefault
隐藏文件SearchedSkipped
二进制文件Prints a match noticeSkipped
并行目录遍历NoYes

速度正源于同样的设计。ripgrep 并行遍历目录,并使用 Rust 的正则引擎进行匹配——它会将模式编译成有限自动机而非回溯,因此病态模式也不会拖垮搜索。跳过被忽略的文件和二进制文件,还意味着从源头上要读取的数据就更少。

这并不代表 grep 已被淘汰。POSIX 对 grep 做了规范,它存在于每一个类 Unix 系统上,这一点在可移植脚本以及无法安装额外软件包的机器上至关重要。当你交互式地搜索项目时使用 rg,而把 grep 留给那些必须在任何地方都能运行的脚本。

安装 ripgrep

在 Ubuntu、Debian 及其衍生发行版上,使用 apt 安装 ripgrep 软件包:

sudo apt install ripgrep

在 Fedora、RHEL 及其衍生发行版上,使用 dnf。在 RHEL 及兼容发行版上,请先为你的发行版版本启用 EPEL 软件仓库:

sudo dnf install ripgrep

在 Arch Linux 上,从官方仓库安装:

sudo pacman -S ripgrep

使用以下命令验证安装:

rg --version

输出会显示已安装的版本:

ripgrep 15.2.0 (rev e89fff89ac)

你的发行版可能附带不同版本,但本指南中的示例使用的都是当前 ripgrep 版本中可用的常见选项。

语法

rg 命令的基本语法为:

rg [OPTIONS] PATTERN [PATH ...]

PATTERN 参数是要搜索的文本或正则表达式。如果省略 PATH,rg 会从当前目录递归搜索。

例如,rg "error" 会搜索当前目录树,而 rg "error" logs/ 则将搜索限定在 logs 目录中。

基础搜索

在当前目录下的所有文件中搜索某个模式:

rg "error"

示例输出:

logs/app.log:42:error: connection refused
logs/app.log:78:error: timeout after 30s
src/main.py:114:raise ValueError("error parsing config")

输出显示文件名、行号和匹配的行。在交互式终端中,rg 默认还会使用颜色,因此在常见的代码搜索工作流中你无需再使用那些递归 grep 的标志。

要在某个特定文件中搜索,请在模式后传入文件名:

rg "error" logs/app.log

要在某个特定目录中搜索,请传入目录路径:

rg "error" logs/

按文件类型过滤

使用 -t 将搜索限定在已知文件类型:

rg -t py "import os"

这样只搜索 Python 文件。ripgrep 内置了许多文件类型的定义。要查看完整列表,可运行:

rg --type-list

要排除某种文件类型,使用 -T:

rg -T js "TODO"

你也可以配合 -g 使用 glob 模式来匹配或排除特定的文件名。请给 glob 模式加上引号,以免 shell 在 rg 收到它们之前就先行展开:

rg -g '*.log' "error"

使用前缀 ! 来排除匹配到的路径:

rg -g '!*.min.js' "console.log"

! 前缀属于 rg 自身,因此在那些 ! 可能触发历史展开的 shell 中,加引号尤为有用。

不区分大小写搜索

加上 -i 以忽略字母大小写:

rg -i "warning"

这会匹配 warning、Warning、WARNING 以及其他大小写变体。

如需更灵活的默认行为,使用 -S 或 --smart-case:

rg -S "warning"

在智能大小写模式下,全小写的模式不区分大小写,而含有任何大写字母的模式则区分大小写。例如,rg -S "warning" 会匹配 Warning,而 rg -S "Warning" 则会按该确切的大小写形式搜索。

固定字符串搜索

默认情况下,rg 将模式视为正则表达式。使用 -F 改为搜索字面字符串,这在模式包含正则字符时非常有用:

rg -F "price[0]"

如果没有 -F,[0] 会被当作字符类解释;而加上 -F 后,rg 会按字面文本 price[0] 进行搜索。

统计与列出匹配项

要统计每个文件中匹配的行数,而不是打印匹配的行,使用 -c:

rg -c "error"

示例输出:

logs/app.log:14
logs/nginx/access.log:3

该计数统计的是匹配行的数量,而非匹配单词或字符串的总数。

要只打印至少包含一个匹配项的文件名,使用 -l:

rg -l "error"

要只打印不包含任何匹配项的文件名,使用 --files-without-match:

rg --files-without-match "error"

这与将 -v 与 -l 组合不同。--files-without-match 选项检查的是某个文件是否具有零个匹配行。

上下文行

当仅凭匹配行不足以理解周围的代码时,可用 -C 增加上下文:

rg -C 3 "panic"

这会打印每个匹配项前后各三行。

仅要匹配项之后的行,使用 -A:

rg -A 2 "def connect"

仅要匹配项之前的行,使用 -B:

rg -B 2 "def connect"

当你想在不打开每个文件的情况下查看函数、错误消息或配置值周围的代码时,上下文输出非常有用。

多个模式

使用 -e 在单次扫描中搜索多个模式:

rg -e "error" -e "warning"

这会匹配包含其中任意一个单词的行。它等价于正则 error|warning,但当模式变长时,重复使用 -e 选项更易读。

-e 选项在模式以连字符开头时也很有用:

rg -e "--force"

如果没有 -e,rg 会试图将 --force 解释为一个选项。

整词匹配

-w 标志将匹配限制为完整单词。当你想查找某个变量名、又不想让它包含在更长的名称中被匹配时,这很有用:

rg -w "id"

这会匹配 id,但不会匹配 uid 或 invalid。

反向匹配

-v 标志打印不匹配模式的行:

rg -v "^#" config.txt

这会显示 config.txt 中所有不以 # 开头的行。

如果你想要的是完全不包含某个模式的文件名,请改用 --files-without-match:

rg --files-without-match "version" -g '*.json'

搜索隐藏文件与忽略 .gitignore

默认情况下,递归的 rg 搜索会跳过隐藏文件和目录,并遵循 .gitignore、.ignore 和 .rgignore 文件。这在源代码树中通常正是你想要的。

要包含隐藏文件和目录,使用 --hidden:

rg --hidden "api_key"

要忽略 .gitignore、.ignore 和 .rgignore 规则,使用 --no-ignore:

rg --no-ignore "TODO"

--no-ignore 选项本身不会包含隐藏文件。若要在同一次搜索中同时搜索隐藏文件和被忽略的文件,请将两个选项组合使用:

rg --hidden --no-ignore "password"
注意:使用 --hidden --no-ignore 进行搜索可能会包含构建产物、依赖目录、缓存文件夹以及其他庞大的目录树。该操作会慢得多,并可能产生嘈杂的结果,因此只有在确有特定理由需要搜索常规项目文件之外的范围时才应使用。

替换输出

-r 选项会在输出中重写匹配的文本,以展示替换后的样子。它不会修改任何文件:

rg "foo" -r "bar"

示例输出:

src/config.py:5:bar = get_setting("bar")

输出展示的是将 foo 替换为 bar 后该行的样子。在使用 sed 或文本编辑器的查找替换功能之前,这可用于预览一次全项目的重命名。

若替换中用到捕获组,请将整条命令用单引号包裹,以免 shell 在 rg 运行之前就展开 $1、$name 之类的替换引用。

只显示匹配内容

默认情况下,rg 会打印包含匹配项的整行。使用 -o 则只打印匹配到的文本:

rg -o 'v[0-9]+\.[0-9]+\.[0-9]+'

示例输出:

package.json:4:v1.4.2
package-lock.json:8:v1.4.2

当你想从文件中提取值、而不是在上下文中查看匹配行时,这很有用。

ripgrep 配置文件

ripgrep 可以从配置文件中读取默认选项,但它不会自动去 ~/.config/ripgrep/ 或任何其他固定路径查找。你必须将 RIPGREP_CONFIG_PATH 指向你希望 rg 读取的文件。

例如,创建一个名为 ~/.ripgreprc 的配置文件:

--smart-case

--hidden

--glob=!.git/

然后导出该环境变量:

export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"

如果你希望该设置在新终端会话中生效,可将这条 export 行添加到你的 shell 启动文件中,例如 ~/.bashrc 或 ~/.zshrc。

配置文件的每一行都会作为一个命令行参数传递给 rg。对于带值的选项,可以在一行内写成 --option=value,也可以将选项与其值分写在两行。

故障排查

rg 找不到你预期的文件:该文件可能是隐藏文件,被 .gitignore、.ignore 或 .rgignore 忽略,或者被识别为二进制文件。先运行 rg --debug "pattern" 查看路径被跳过的原因,然后只在确有需要时再加上 --hidden、--no-ignore 或两者同时加上。

glob 模式没有达到预期效果:请为 glob 模式加上引号,例如 rg -g '*.conf' "server" 与 rg -g '!*.min.js' "console.log"。如果不加引号,你的 shell 可能会在 rg 看到该模式之前就展开其中的 *。

以 - 开头的模式被当作选项处理:在模式前使用 -e,例如 rg -e "--force"。你也可以使用 -- 来停止选项解析,如 rg -- "--force"。

选项参考

  • -t TYPE - 只搜索指定类型的文件。
  • -T TYPE - 排除指定类型的文件。
  • -g GLOB - 按 glob 包含或排除文件。前缀 ! 表示排除路径。
  • -i - 不区分大小写搜索。
  • -F - 将模式视为固定字符串而非正则表达式。
  • -c - 统计每个文件中匹配的行数。
  • -l - 只列出含匹配项的文件名。
  • --files-without-match - 只列出不含匹配项的文件名。
  • -C N - 显示每个匹配项前后各 N 行上下文。
  • -A N - 显示每个匹配项之后的 N 行。
  • -B N - 显示每个匹配项之前的 N 行。
  • -e PATTERN - 添加一个搜索模式。重复使用可搜索多个模式。
  • -w - 只匹配完整单词。
  • -v - 反向匹配并打印不匹配的行。
  • -o - 只打印匹配到的文本,而非整行。
  • -r REPLACEMENT - 显示替换匹配项后的输出。仅为预览。
  • -n - 显示行号。在交互式终端输出中默认启用。
  • --hidden - 搜索隐藏文件和目录。
  • --no-ignore - 不遵循 .gitignore、.ignore 与 .rgignore 规则。
  • -S, --smart-case - 模式全小写时不区分大小写,含大写时则区分大小写。
  • --stats - 在末尾打印搜索摘要。
  • -M N - 省略长度超过 N 字节的行。

快速参考

如需可打印的快速参考,请参阅 ripgrep 速查表。

TaskCommand
从当前目录递归搜索rg "pattern"
搜索特定文件或目录rg "pattern" logs/
只搜索单一文件类型rg -t py "pattern"
按 glob 过滤,或用 ! 排除rg -g '*.log' "pattern"
使用智能大小写匹配rg -S "pattern"
显示三行上下文rg -C 3 "pattern"
列出含匹配项的文件名rg -l "pattern"
包含隐藏文件与被忽略文件rg --hidden --no-ignore "pattern"
预览替换结果rg "old" -r "new"

常见问题

rg 比 grep 更快吗?在大型目录树上,几乎总是更快。ripgrep 并行遍历目录,并跳过被忽略的、隐藏的和二进制的文件,因此它读取的数据远少于对同一项目的递归 grep。当面对单个小文件加简单的字面模式时,差距会缩小——此时 GNU grep 的速度已接近读取文件本身。让 rg 在日常使用中感觉更快的,是其默认行为,而不仅仅是原始匹配速度。

rg 命令会搜索隐藏文件吗?在递归搜索过程中不会。rg 会跳过点文件和点目录,并且遵循 .gitignore、.ignore 与 .rgignore。这是两道相互独立的过滤器:--hidden 添加隐藏文件,而 --no-ignore 关闭忽略文件规则。-u 标志是叠加它们的简写,其中 -u 等价于 --no-ignore,-uu 等价于 --no-ignore --hidden,-uuu 还会额外包含二进制文件。

rg --files 是做什么的?它会打印出 rg 本应搜索的每一个文件,然后退出而不搜索其中任何一个。这是在运行真正搜索之前,确认某个类型或 glob 过滤器是否选出了预期文件的最快方式,例如 rg --files -t py。将它通过管道传给 wc -l 即可获得当前过滤器下的文件数量。

我究竟如何用 rg 真正替换文本?-r 选项只能预览替换,因此你需要借助第二个工具来实际写入变更。先用 rg -l "old" . 预览受影响的文件。

在执行原地替换之前,请先提交或备份这些文件。待文件列表确认无误后,将其传给 sed:

rg -l -0 'old' . | xargs -0 -r sed -i.bak 's/old/new/g'

-0 选项能完整保留包含空格、引号或换行符的文件名。GNU xargs 的 -r 会在没有文件匹配时跳过该命令,而 sed -i.bak 会在编辑前为每个原始文件保存一份带 .bak 后缀的副本。

总结

ripgrep 覆盖了与 grep 相同的领域,但其递归搜索、文件过滤以及对忽略文件的支持,使其成为许多代码与日志搜索场景中更好的默认选择。对于基于路径的文件查找,可将其与 find 搭配使用;而当你需要一份包含特定模式的文件清单时,则使用 rg -l。