
明明 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.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 多个文件?
--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、.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,留给那种要翻个底朝天的时刻。