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

仙人之下我无敌,
仙人之上一换一。

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

rg 搜不到文件,是忽略规则在挡

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

rg 搜不到文件,是忽略规则在挡

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

差了多少个文件?

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

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

grep -rn "NEEDLE" . 在同样的目录里找到 7 个文件加一个二进制文件。被 rg 跳过的是 .env.localsecret.txtbuild/out.txtvendor/lib.txtnode_modules/.bin/tool.txt

这套素材里写了三份忽略配置:.gitignore 里是 secret.txtbuild/.ignore 里是 vendor/.env.localnode_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.txtbuild/out.txt--no-ignore.ignore 也一起关,vendor/lib.txt 回来了,正好 6 个。

--hidden 为什么一下多出 40 多个文件?

--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.txtb.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 才能看到它。所以在一个大仓库的深层目录里搜东西时,规则的来源可能在上面好几层,光看当前目录的配置文件看不出来。--debugfrom 字段这时就派上用场了,它会告诉你规则文件的确切路径。

加了 ! 为什么还是救不回来?

目录被整体排除时,里面的文件救不回来。.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.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,留给那种要翻个底朝天的时刻。

    🤞 分享