CLI AI

递归 grep 时排除某个目录

2026-06-08

grep -rn --exclude-dir={.git,node_modules,vendor} 'TODO' . 能找到所有匹配,同时跳过三个几乎会毁掉任何真实仓库递归 grep 的目录。--exclude-dir 只有配合 -r 才生效,而且比较的是裸目录名,不是路径。下面是完整方案:从最原始的递归搜索,到该直接装 ripgrep 的那一刻。

1. 搜索整个项目

clai
$ clai 在 g 目录下所有文件中查找 TODO→ grep -rn 'TODO' g/g/src/a.js:1:TODO fixg/node_modules/b.js:1:TODO dep

-r 递归进入子目录,-n 加上行号。问题立刻就能看出来:一半的结果来自别人的代码。

2. 排除一个目录

clai
$ clai 查找 TODO,但不要进入 node_modules→ grep -rn --exclude-dir=node_modules 'TODO' g/g/src/a.js:1:TODO fix

--exclude-dir 只有配合 -r 才起作用。目录名是一个匹配模式,不是路径——不要加斜杠。

3. 一次排除多个目录

clai
$ clai 查找 TODO,跳过 node_modules、.git 和 vendor→ grep -rn --exclude-dir={.git,node_modules,vendor} 'TODO' .

花括号在 grep 运行之前就被 shell 展开了,所以 grep 拿到的是三个独立的参数。在不支持花括号展开的 sh 里,需要连写三个 --exclude-dir。

4. 只在需要的文件里搜索

clai
$ clai 只在 javascript 文件里查找 TODO→ grep -rn --include='*.js' 'TODO' .

--include 是白名单。当你已经知道该往哪里找时,它比一长串排除规则更短、更快。

5. 不被二进制文件绊倒

clai
$ clai 查找 TODO,忽略二进制文件→ grep -rnI 'TODO' .

不加 -I,grep 在二进制文件里每次匹配都会打印「Binary file … matches」而不是具体的行——这会把输出弄乱,也会破坏下游脚本的解析。-I 直接跳过这些文件。

6. 参数越写越多的时候

clai
$ clai 在项目里查找 TODO,同时遵守 gitignore→ rg 'TODO'

ripgrep 默认读取 .gitignore、跳过 .git、跳过二进制文件——上面这些事它一个参数都不用就全做了,而且更快。如果你今天已经是第三次写很长的 --exclude-dir 链条,是时候装它了。

常见坑

  • --exclude-dir 不配合 -r 会悄悄失效。 参数会被接受,但从不生效,你会以为它坏了。
  • --exclude-dir 里的模式不是路径。 --exclude-dir=src/vendor 匹配不到任何东西,因为 grep 比较的是目录名,不是到它的路径。写成 --exclude-dir=vendor。
  • 模式必须加引号。 grep -r --include=*.js 会让 shell 在 grep 看到之前,先用当前目录下的文件展开 *.js,结果搜索的范围完全不对。

相关问题

怎么排除 .git 目录本身? --exclude-dir=.git。开头的点不影响匹配——在仓库里不加这个参数直接跑 -r,会把 git 对象库里的垃圾也翻出来。

怎么统计匹配数量而不是打印它们? grep -rc 'TODO' . 输出每个文件的匹配数;grep -rho 'TODO' . | wc -l 给出总匹配次数。

grep 还是 ripgrep? grep 到处都有,值得掌握。ripgrep 更快,默认行为也更合理,但需要额外安装,在别人的服务器上未必能装。

相关阅读

别再死记硬背排除参数了——描述你要找什么,CliAI 帮你写 grep 命令。一行命令安装。