仅在目录的指定文件名中递归搜索模式/文本?

我有一个目录(例如, abc/def/efg )有很多子目录(例如: abc/def/efg/(1..300) )。 所有这些子目录都具有公共文件(例如, file.txt )。 我想只搜索此file.txt的字符串,不包括其他文件。 我怎样才能做到这一点?

我使用grep -arin "pattern" * ,但如果我们有很多子目录和文件,它会非常慢。

在父目录中,您可以使用find然后仅对这些文件运行grep

 find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' + 

你也可以使用globstar。

与Zanna的答案一样 , 使用find构建grep命令是一种非常强大,通用且便携的方法(参见sudodus的答案 )。 而且muru发布了一个使用grep--include选项的优秀方法 。 但是如果你只想使用grep命令和shell,还有另一种方法 – 你可以让shell本身执行必要的递归

 shopt -s globstar # you can skip this if you already have globstar turned on grep -H ' pattern ' **/file.txt 

即使只找到一个匹配的文件, -H标志也会使grep显示文件名。 您可以将-a-i-n标志(从您的示例中)传递给grep ,如果这是您需要的。 但是在使用此方法时不要传递-r-R 。 在扩展包含**的glob模式而不是grepshell会递归目录。

这些说明特定于Bash shell。 Bash是Ubuntu(以及大多数其他GNU / Linux操作系统)中的默认用户shell,所以如果你在Ubuntu上并且不知道你的shell是什么,那几乎可以肯定是Bash。 虽然流行的shell通常支持目录遍历** globs,但它们并不总是以相同的方式工作。 有关更多信息,请参阅StéphaneChazelas对Unix.SE 上ls *,ls **和ls ***结果的出色回答 。

这个怎么运作

打开globstar bash shell选项会使**匹配路径包含目录分隔符( / )。 因此它是一个目录递归的glob。 具体来说,正如man bash所说:

启用globstar shell选项并在路径名扩展上下文中使用*时,用作单个模式的两个相邻*将匹配所有文件以及零个或多个目录和子目录。 如果后跟一个/,则两个相邻的* s将仅匹配目录和子目录。

你应该小心这一点,因为你可以运行修改或删除远远超过你想要的文件的命令,特别是当你打算写* 。 (在这个命令中它是安全的,它不会改变任何iles。) shopt -u globstar将globstar shell选项关闭。

globstar和find之间存在一些实际差异。

find比globstar更通用。 你可以使用globstar做任何事情,你也可以使用find命令。 我喜欢globstar,有时它更方便,但是globstar并不是find一般选择。

上面的方法不会查找名称以a开头的目录. 。 有时您不想递归此类文件夹,但有时您会这样做。

与普通的glob一样,shell构建了一个包含所有匹配路径的列表,并将它们作为参数传递给您的命令( grep )来代替glob本身。 如果你有这么file.txtfile.txt文件,结果命令太长,系统无法执行,那么上面的方法就会失败。 在实践中,你需要(至少)数千个这样的文件,但它可能会发生。

使用find的方法不受此限制,因为:

  • Zanna的方式构建并运行一个带有可能很多路径参数的grep命令。 但是,如果找到的文件多于单个路径中列出的文件,则+ -terminated -exec操作会运行带有某些路径的命令,然后再使用一些路径再次运行它,依此类推。 在grep ing多个文件中的字符串的情况下,这会产生正确的行为。

    就像这里介绍的globstar方法一样,这会打印所有匹配的行,每个行前面都有路径。

  • sudodus的方法分别为每个file.txt运行grep 。 如果有很多文件,它可能比其他一些方法慢,但它可以工作。

    该方法查找文件并打印其路径,然后匹配行(如果有)。 这是与我的方法Zanna和muru的格式不同的输出格式。

通过find获得颜色

使用globstar的一个直接好处是,默认情况下,在Ubuntu上, grep将产生彩色输出。 但你也可以轻松地找到它

Ubuntu中的用户帐户创建了一个别名 ,使grep真正运行grep --color=auto (运行alias grep看看)。 当交互式发布别名时 ,别名几乎只会扩展,这是一件好事 ,但这意味着如果你想让find使用--color标志调用grep ,你必须明确地写它。 例如:

 find . -name file.txt -exec grep --color=auto -H ' pattern ' {} + 

你不需要find这个; grep可以自己完美处理:

 grep "pattern" . -airn --include="file.txt" 

man grep

 --exclude=GLOB Skip files whose base name matches GLOB (using wildcard matching). A file-name glob can use *, ?, and [...] as wildcards, and \ to quote a wildcard or backslash character literally. --exclude-from=FILE Skip files whose base name matches any of the file-name globs read from FILE (using wildcard matching as described under --exclude). --exclude-dir=DIR Exclude directories matching the pattern DIR from recursive searches. --include=GLOB Search only files whose base name matches GLOB (using wildcard matching as described under --exclude). 

在muru的回答中给出的方法是 ,使用--include标志运行grep来指定文件名,通常是最好的选择。 但是,这也可以通过find来完成。

本回答中的方法使用find分别为找到的每个文件运行grep ,并在每个文件中找到的匹配行上方精确打印每个文件的路径一次 (在其他答案中介绍了在每个匹配行前面打印路径的方法。)


您可以将目录更改为您拥有这些文件的目录树的顶部。 然后运行:

 find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \; 

这将打印名为file.txt的每个文件的路径(相对于当前目录,包括文件名本身),然后是文件中所有匹配的行。 这是有效的,因为{}是找到的文件的占位符。 每个文件的路径通过前缀#####与其内容分开设置,并且在该文件的匹配行之前仅打印一次。 (名为file.txt且不包含匹配项的文件仍会打印其路径。)您可能会发现此输出比在每个匹配行的开头打印路径的方法更简洁。

使用像这样的find几乎总是比在每个文件上运行grep更快( grep -arin "pattern" * ),因为find搜索具有正确名称的文件并跳过所有其他文件。

Ubuntu使用GNU find ,它总是扩展{}即使它出现在一个更大的字符串中 ,比如##### {}: . 如果您需要使用命令在可能不支持此function的系统上使用find ,或者您希望仅在绝对必要时使用-exec操作,则可以使用:

 find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \; 

要使输出更易于阅读 ,可以使用ANSI转义序列来获取彩色文件名。 这使得每个文件的路径标题在其下面打印的匹配行中更加突出:

 find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \; 

这会导致shell将绿色的转义码转换为在终端中生成绿色的实际转义序列,并使用正常颜色的转义码执行相同的操作。 这些转义传递给find ,它在打印文件名时使用它们。 (此处需要$' '引用,因为find-printf操作无法识别\e来解释ANSI转义码。)

如果您愿意,可以使用-exec和系统的printf命令 (支持\e )。 所以另一种做同样事情的方法是:

 find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \; 

只是指出如果问题的条件可以采取文学,你可以使用直接grep:

 grep 'pattern' abc/def/efg/*/file.txt 

要么

 grep 'pattern' abc/def/efg/{1..300}/file.txt