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

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

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

同一棵树,grep -r 命中 3 行,rg -L 命中 7 行

2026-9-27 / 0 评论 / 3 阅读

同一棵树,grep -r 命中 3 行,rg -L 命中 7 行

一份配置文件在目录里,路径最后一跳是软链,内容确实有关键字。在这棵树里递归搜索,一行都搜不到。这类「文件在那儿,关键字也在」的落差,多数不是权限问题,是搜索工具按设计决定不跟进去。

手册里是怎么写的

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 的代价,比事后解释「为什么漏了几个文件」小得多。

    🤞 分享