
一份配置文件在目录里,路径最后一跳是软链,内容确实有关键字。在这棵树里递归搜索,一行都搜不到。这类「文件在那儿,关键字也在」的落差,多数不是权限问题,是搜索工具按设计决定不跟进去。
手册里是怎么写的
macOS 自带的是 BSD grep,版本串上写着 GNU compatible,但递归与符号链接相关的开关和 GNU 那套不一样。
== 7. man grep 里关于递归与符号链接的原文 ==
-R, -r, --recursive
Recursively search subdirectories listed. (i.e., force grep to
behave as rgrep).
-S If -R is specified, all symbolic links are followed. The default
is not to follow symbolic links.
-s, --no-messages
两处细节值得抄下来:-R 与 -r 并列在同一行里,是同一个开关的两种写法;跟随符号链接的开关叫 -S,而且它的说明以 If -R is specified 开头。也就是说 -S 不是一个独立开关,它只在同时给了 -r 或 -R 的时候才起作用。
各个开关数同一棵树
树里有三个真文件放在 real/ 下,一个指向 real 的软链叫 link,一个指向 real/a.log 的软链叫 link-to-file.log,总共四行能命中。
== 2. 在当前目录递归,各个开关命中几行 ==
grep -r needle . -> 3 行
grep -R needle . -> 3 行
grep -S needle . -> 0 行
grep -rS needle . -> 7 行
grep -RS needle . -> 7 行
同一棵树,同一条命令,五种写法给出两种答案。-r 与 -R 都是 3 行,-rS 与 -RS 都是 7 行。7 行是多出来的:软链指过去的同一份内容被数了第二遍,加上那个被软链挂进来的文件。
单给 -S 是 0 行,这一格单独看:没有 -r/-R,grep 把目录当作一个普通文件参数,什么都没搜到。手册那句 If -R is specified 在这里落成了实实在在的 0。
命令行上给的链接算例外
软链目录写在命令行上时,结果不一样。
== 3. 把符号链接目录写在命令行上 ==
grep -r needle link/ -> 3 行
grep -R needle link/ -> 3 行
grep -S needle link/ -> 0 行
grep -r needle link/ 能搜进去,拿到 3 行。区别在于这个软链是起点还是路上遇到的:起点会跟进去,递归过程中遇到的目录软链不跟。踩过这件事的人往往记住的是「grep -r 能进我的软链目录」,然后在另一个目录上失效,因为那一次它是路上遇到的。
find 与 ripgrep 的两个对照
find 默认也不跟,要加 -L,手册写得直白:把每个符号链接的文件信息换成它指向的目标。
== 5. find 要加 -L ==
find . -name "*.log" -> 3 个
find -L . -name "*.log" -> 5 个
3 个变成 5 个:软链指过去的两个 .log 文件被算进来了,中间那个 link-to-file.log 本来就在 3 个里。
ripgrep 的默认值和它们一致,要有 -L 或 --follow 才进链接。
== 6. ripgrep(ripgrep 15.2.0)==
rg needle . -> 3 行
rg -L needle . -> 7 行
== 9. rg --help 里 --follow 的原文 ==
-L, --follow
This flag instructs ripgrep to follow symbolic links while traversing
directories. This behavior is disabled by default. Note that ripgrep
三个工具在同一件事上的默认值是统一的:递归不跟随符号链接。这不是 bug,是绕开重复遍历与链接环路的老规矩。
要搜到它们,加什么
grep -rS 'needle' . # BSD grep:-r 与 -S 一起给
rg -L 'needle' . # ripgrep:跟随符号链接
find -L . -name '*.log' # find:-L 写在路径之前
grep -r 'needle' "$(readlink link)" # 绕开:直接搜软链指向的真实目录
如果搜索结果要拿去做统计、写进发布脚本或者判断「文件少了没」,建议先用一个已知的软链目录跑一遍对照,确认这次的搜索起点是哪个。另外,GNU grep 的 -r 与 -R 是有区分的,-R 会跟随全部符号链接,同一段命令换到 Linux 上行为会变。这一点没在 Linux 上实测,按手册的说法是这么分的。
排查时有一句很好用:find . -type l 会把树里所有符号链接列出来,一眼就能看出哪些路径是挂进来的。如果某个目录的内容主要靠软链组装(部署目录里很常见),递归搜索的结果就不能当成「这里没有」。
find . -type l # 列出树里所有符号链接
find . -type l -exec ls -l {} + # 连同它们指向哪里一起看
我倾向于让脚本里的搜索起点写成解析之后的真实路径:软链怎么挂都不影响结果,换机器、换 grep 版本也一样。多写一行 readlink 的代价,比事后解释「为什么漏了几个文件」小得多。