grep指定目录与glob通配符的写法差异解析
三个命令的核心差异来自Shell对传入路径的通配符展开规则,和grep自身的-r递归逻辑无关:
grep -rni . -e "keyword"
直接将当前目录.作为检索起点传入grep,由grep自行控制全量递归遍历,会覆盖当前目录下所有文件、子目录(包括点开头的隐藏文件、隐藏目录,比如.git目录、.env文件),遍历效率最高,是递归检索的标准写法。grep -rni * -e "keyword"
命令执行前,Shell会先把*展开为当前目录下所有非隐藏的文件、子目录列表,再将展开后的路径列表传给grep,grep再对列表中的目录执行递归检索。这种写法默认不会匹配点开头的隐藏条目,你测试时发现和第一条结果数一致,只是因为当前目录下的隐藏条目没有命中关键词而已;两者结果顺序不同,是因为第一条按grep读取目录的原生顺序输出,第二条按Shell展开通配符时的文件名排序顺序输出。grep -rni **/* -e "keyword"
这里的**是Shell的扩展递归通配符(globstar),仅当手动开启对应Shell配置(bash下执行shopt -s globstar、zsh下执行setopt globstar)时才会生效,生效后**会递归匹配所有层级的子目录。你测试时该命令返回结果远多于前两条,核心原因不是特殊匹配规则,而是产生了大量重复遍历:Shell展开
**/*时,会把所有层级的子目录、所有子文件都作为独立路径传给grep。grep拿到路径列表后,会对每一个传入的目录单独再做一次-r递归扫描。举个实际例子,假设存在路径
./src/utils/str.js包含关键词,**/*展开后会把src、src/utils、src/utils/str.js三个路径都传给grep:- 扫描
src目录时,会递归命中src/utils/str.js一次 - 扫描
src/utils目录时,会再次递归命中src/utils/str.js一次 - 直接扫描
src/utils/str.js文件时,会第三次命中该文件
路径深度越大,同一个文件被重复扫描的次数越多,最终输出的匹配结果自然远多于前两条命令。如果没有开启globstar,**的行为和普通*一致,此时**/*仅会匹配一级子目录下的内容,结果数反而会比前两条更少。
- 扫描
第三条命令的适用场景
日常检索几乎没有使用第三条命令的合理场景,这种写法既会产生大量重复扫描拖慢效率,还可能因为通配符展开规则的差异漏过文件。如果需要限定递归检索的路径范围,直接给grep传入明确的起点路径即可,不需要靠Shell展开多层通配符传参。
内容的提问来源于stack exchange,提问作者WalksB

