仅在目录的指定文件名中递归搜索模式/文本?
我有一个目录(例如, 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模式而不是grep
, shell会递归目录。
这些说明特定于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.txt
为file.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