侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 259 篇文章
  • 累计收到 7 条评论

rg 搜不到不是文件的问题,先问是哪条规则挡的

2026-9-15 / 0 评论 / 120 阅读

rg 搜不到不是文件的问题,先问是哪条规则挡的

明明 cat 能打出那个字符串,rg 就是搜不到。换成 grep -r 立刻就有结果。这种时候九成不是 rg 出问题,是某个忽略规则把文件挡在门外了,而 rg 默认不会告诉你。这些问题都是排查时一条条问出来的,答案都在同一棵目录里跑过:/tmp 下 10 个文件,macOS 26.2 上的 ripgrep 15.2.0,脚本存档在 tools/verify-rg-ignore.sh。

同一棵目录,rg 看到 3 个,grep -r 看到 7 个

同一棵目录,rg --files 只列出 3 个,另外 5 个它一个字都不提,换成 grep -rn 全都能搜到。

app/keep.txt
img.bin
node_modules/dep.txt

grep -rn "NEEDLE" . 在同样的目录里找到 7 个文件加一个二进制文件。被 rg 跳过的是 .env.local、secret.txt、build/out.txt、vendor/lib.txt、node_modules/.bin/tool.txt。

这套素材里写了三份忽略配置:.gitignore 里是 secret.txt 和 build/,.ignore 里是 vendor/,.env.local 和 node_modules/.bin 属于隐藏文件,没有任何规则提到它们。

让 rg 自己交代是谁挡的

排查第一步是带上 --debug,它会把每一条跳过决定和规则出处都打出来,不用靠猜。

ignoring ./.env.local: Ignore(IgnoreMatch(Hidden))
ignoring ./.ignore: Ignore(IgnoreMatch(Hidden))
ignoring ./secret.txt: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore")
ignoring ./.gitignore: Ignore(IgnoreMatch(Hidden))
ignoring ./build: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore")
ignoring ./.git: Ignore(IgnoreMatch(Hidden))
ignoring ./vendor: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.ignore")
ignoring ./node_modules/.bin: Ignore(IgnoreMatch(Hidden))

读法是看 IgnoreMatch 那一段。Hidden 表示它是个隐藏文件或者隐藏目录,.gitignore、.ignore 那些带 from 的表示命中了某个忽略文件里的某条规则,from 后面的路径就是规则来源。输出里 Some("./.gitignore") 只截了一半,原文还会带上 original: 和 actual:,能看到自己写的 build/ 被展开成了 **/build。

排查时用的命令行是:

rg --debug "NEEDLE" 2>&1 | grep -i "ignor"

开关一个个加上去,各放行多少文件

同样的目录,逐个开关加上去,文件数是这样变的:

rg --files (默认)             -> 3 个文件
rg --files --no-ignore-vcs     -> 5 个文件
rg --files --no-ignore         -> 6 个文件
rg --files --hidden            -> 49 个文件
rg --files --no-ignore --hidden -> 52 个文件
rg --files -uu                 -> 52 个文件
rg --files -L                  -> 4 个文件

--no-ignore-vcs 只放行 git 那套规则,多出来的两个是 secret.txt 和 build/out.txt。--no-ignore 连 .ignore 也一起关,vendor/lib.txt 回来了,正好 6 个。

--hidden 多出来的 40 多个文件都在 .git 里

--hidden 那几个数字要看清:49 个里有 42 个在 .git 目录里面(这个数量会随仓库里的对象数变化,别当固定值)。它不管当前目录是不是版本库,隐藏目录一律往下走。所以用 --hidden 时都会先确认搜索范围里没有版本库,或者干脆换个更窄的目录再搜。

-uu、-uuu、-L 三个开关的差别

-uu 是 --no-ignore --hidden 的简写,结果和两个都写上一致。-uuu 在这个目录里数字相同,区别是它额外带上 --binary,效果后面说。

-L 只多了 1 个文件,那是符号链接指向的目录。rg 默认不跟着符号链接走,link-target/pointing-to-app/keep.txt 只有加了 -L 才会出现在结果里。

别直接上 -uuu 图省事

我不建议这么干。它的效果是把所有过滤都摘掉,包括本来想排除的构建产物和大目录,搜一次能扫出几万个匹配,最后还是得回来加 -g 过滤。

二进制文件里的匹配只给一行提示

img.bin 里塞了一个带 \0 字节的匹配。默认搜索时它根本不在结果里,加 -uu 之后 rg 会给一行提示:

img.bin: binary file matches (found "\0" byte around offset 0)

它承认匹配到了,但不把内容吐出来。要真的看到字节得再加 --text:

img.bin:^@^A^BNEEDLE^C

-uuu 是在 -uu 之上再加 --binary:二进制文件会被扫描,命中时给一行提示,内容仍然不打印出来。实测把 -uuu 和 -uu --text 的输出做 diff,两者不一样,想看二进制里的匹配得显式加 --text。

临时搜一个被忽略的文件

临时想搜一个被忽略的文件,不用去改 .gitignore:

rg --files -g 'secret.txt'

这条直接输出 secret.txt,虽然 .gitignore 里明确写着忽略它。glob 白名单比忽略规则的优先级高,也是排查时最顺手的一招,搜完即走,不留痕迹。

反过来限定搜索范围用它也顺手,rg "NEEDLE" -g 'app/**' 只会在 app 目录里找。

没有 git 仓库时,.gitignore 形同虚设

这条最容易误判。在一个干净目录里放了 a.txt、b.txt 和一个写着 b.txt 的 .gitignore:

--- 没有 .git 时 rg --files ---
a.txt
b.txt
--- 本目录 git init 之后 rg --files ---
a.txt
--- 改用 .ignore(不走 git 那套)---
a.txt

没有 .git 目录时,.gitignore 被完整忽略,两个文件都在列表里。git init 之后规则立刻生效。换成 .ignore 的话不需要仓库,照样生效。

推论很实用:把一份代码拷到部署机上,或者解压一个发布包,那份目录通常没有 .git,这时候拿 .gitignore 排除大目录的想法是落空的,得换成 .ignore。

父目录的规则一路管到子目录

忽略文件的作用范围是它所在的目录以及所有子目录。在 /tmp/rg-parent 里放 .gitignore 写 *.tmp,然后进到 deep/nested 搜索:

--- rg --files ---
keep.txt
--- rg --files --no-ignore-parent ---
cache.tmp
keep.txt

cache.tmp 被两层之外的规则挡住了,加 --no-ignore-parent 才能看到它。所以在一个大仓库的深层目录里搜东西时,规则的来源可能在上面好几层,光看当前目录的配置文件看不出来。--debug 的 from 字段这时就派上用场了,它会告诉你规则文件的确切路径。

目录被整体排除,! 也救不回来

目录被整体排除时,里面的文件救不回来。.gitignore 里写:

keepdir/
!keepdir/x.txt

keepdir 下的命中文件数是 0。把规则改成 keepdir/* 再配同一条取反,keepdir/x.txt 就回来了。

这不是 rg 的怪癖,git 自己的判断完全一样(脚本存档 tools/verify-gitignore-negation.sh):

===== .gitignore 内容:keepdir/ 加 !keepdir/x.txt =====
--- git status --short --ignored ---
?? .gitignore
!! keepdir/

===== 改成 keepdir/* 加 !keepdir/x.txt =====
--- git status --short --ignored ---
?? .gitignore
?? keepdir/
!! keepdir/y.txt

前一组里 !! keepdir/ 说明整个目录被排除,x.txt 没被救回来;后一组把排除写成通配之后,x.txt 变成可添加状态,只剩 y.txt 还躺着。规则的解释是:目录被整体排除之后,它下面的路径不再参与匹配判断,所以写在后面的取反没有机会生效。很多人以为加个 ! 就能白名单某个文件,得先把排除写成 keepdir/* 这种通配形式。

.rgignore 压 .ignore,.ignore 压 .gitignore

拿同一个文件试着被忽略、被放行,结果是 .rgignore 压 .ignore,.ignore 压 .gitignore:

--- 只有 .gitignore 忽略 both.txt:[]
--- .ignore 里写 !both.txt 之后:[both.txt]
--- .ignore 写回忽略、.rgignore 写 !both.txt:[both.txt]

这条用来解决一个具体问题:想加一条只对自己生效的忽略规则,又不想动仓库里的 .gitignore。

搜不到东西时的排查顺序

搜不到东西时先跑一次带 --debug 的搜索,看匹配到的是 Hidden 还是某个忽略文件,from 指向哪里。规则来自隐藏目录就加 -g 限定范围,或者配 --hidden 加更窄的路径;规则来自别人写的忽略文件就用 -g 临时放行。

要长期生效的规则写进 .ignore 或者 .rgignore,别指望 .gitignore 在所有场景下都管用,它只在版本库里有效。至于 -uuu,留给那种要翻个底朝天的时刻。

    🤞 分享