什么可以递归地扩展到当前目录中的所有文件?

106

我知道 **/*.ext 可以扩展到匹配所有子目录中的 *.ext 文件,但是有没有类似的扩展方式可以包括当前目录中的这些文件呢?


4
我的bash无法处理**/*.ext这个命令,你确定它可以在你的电脑上正常运行吗? - tangens
1
@tangens 您需要按照Dennis的答案启用globstar选项。 - kenorb
5个回答

127

这将在Bash 4中运行:

ls -l {,**/}*.ext

为了让双星号通配符生效,需要设置 globstar 选项(默认已启用):

shopt -s globstar

来自 man bash:

    globstar
                  如果设置了该选项,则在文件名扩展上下文中使用的模式**将匹配一个或多个目录和子目录中的文件。如果模式后跟一个 /,则仅匹配目录和子目录。

现在我在想,可能曾经存在过globstar处理中的错误,因为现在只是使用简单的ls **/*.ext就可以得到正确的结果。

无论如何,我看了一下kenorb对VLC存储库进行的分析,发现那个分析和我刚才的回答中有一些问题:

find命令的输出进行比较是无效的,因为指定-type f不包括其他文件类型(特别是目录),而列出的ls命令可能会包括。此外,列出的命令之一:ls -1 {,**/}*.*——似乎是基于我的命令——仅对那些位于子目录中的文件输出包含一个点的名称。OP的问题和我的答案包括一个点,因为寻找的是具有特定扩展名的文件。

最重要的是,使用ls命令和globstar模式**存在一个特殊问题。由于Bash将该模式扩展为正在检查的树中的所有文件名(和目录名),因此会出现许多重复项。在扩展之后,ls命令会列出每个它们和它们的内容(如果它们是目录)。

例如:

在我们当前的目录中有子目录A及其内容:

A
└── AB
    └── ABC
        ├── ABC1
        ├── ABC2
        └── ABCD
            └── ABCD1

在那个树形结构中,** 扩展为 "A A/AB A/AB/ABC A/AB/ABC/ABC1 A/AB/ABC/ABC2 A/AB/ABC/ABCD A/AB/ABC/ABCD/ABCD1"(共 7 个条目)。如果你执行 echo **,那么这就是你会得到的精确输出,并且每个条目只被表示一次。然而,如果你执行 ls **,它将输出每个这些条目的列表。所以本质上它会先执行 ls A,再执行 ls A/AB 等等,因此 A/AB 会被显示两次。此外,ls 会将每个子目录的输出分开:

...
<blank line>
directory name:
content-item
content-item

使用 wc -l 命令会计算所有的空白行和目录名称部分标题,这会导致计算结果更加不准确。

这是为什么你不应该 解析 ls 的又一个原因。

综上所述,基于进一步的分析,我建议除了以这种方式迭代文件树之外,不要在任何情况下使用 globstar 模式:

for entry in **
do
    something "$entry"
done

最后做个比较,我使用了一个我手头方便的Bash源代码仓库,并进行了以下操作:

shopt -s globstar dotglob
diff <(echo ** | tr ' ' '\n') <(find . | sed 's|\./||' | sort)
0a1
> .

我使用了tr命令将空格转换为换行符,因为在这里没有名称包含空格。我使用sed命令从每个find输出行中删除前导的./。我排序了find的输出,因为它通常是未排序的,并且Bash的通配符展开已经排序。正如您所看到的,diff的唯一输出是由find输出的当前目录.。当我执行ls ** | wc -l时,输出行数几乎是两倍。


6
我测试了Ubuntu和Cygwin,发现globstar默认是关闭的。 - Zombo
13
最佳答案!但我认为 **/*.ext 应该足够了。另外,除非你使用 shopt -s dotglob,否则你将无法获取隐藏的文件。 - gniourf_gniourf
2
禁用 globstarshopt -u globstar - kenorb
5
实际上这个问题要求特别包含当前目录,因此**/*.ext不够用。 - msciwoj
2
似乎macOS自带的bash版本有缺陷。{*,**/*}只匹配当前目录及其子目录,但不包括这些子目录的子目录。而且shopt列表中没有globstar选项。我已经尝试了homebrew提供的bash shell(4.4.23(1)),并开启了globstar选项,但它对我的glob模式也是一样的结果。 - dotnetCarpenter
显示剩余11条评论

17
您可以使用:**/*.*递归包含所有文件(启用方法:shopt -s globstar)。 其他变式的行为如下: 在具有3472个文件的VLC存储库文件夹中测试: (根据命令find . -type f | wc -l计算出的总文件数为3472) - ls -1 **/*.*返回3338 - ls -1 {,**/}*.*返回3341(由Dennis提出) - ls -1 {,**/}*返回8265 - ls -1 **/*返回7817(除隐藏文件外,由Dennis提出) - ls -1 **/{.[^.],}*返回7869(由Dennis提出) - ls -1 {,**/}.?*返回15855 - ls -1 {,**/}.*返回20321 因此,我认为最接近递归列出所有文件的方法是第一个示例(**/*.*)按照gniourf-gniourf的评论(假设文件具有正确的扩展名,或使用特定的扩展名),因为第二个示例会产生更多重复项。
$ diff -u <(ls -1 {,**/}*.*) <(ls -1 **/*.*)
--- /dev/fd/63  2015-04-19 15:25:07.000000000 +0100
+++ /dev/fd/62  2015-04-19 15:25:07.000000000 +0100
@@ -1,6 +1,4 @@
 COPYING.LIB
-COPYING.LIB
-Makefile.am
 Makefile.am
@@ -45,7 +43,6 @@
 compat/tdestroy.c
 compat/vasprintf.c
 configure.ac
-configure.ac

而另一个生成更多的副本。


若要包含隐藏文件,请使用:shopt -s dotglob(通过shopt -u dotglob禁用)。这不是推荐的做法,因为它可能会影响像mvrm这样的命令,并且您可能会意外地删除错误的文件。


1
在启用globstar的Mac终端和bash中,我发现上述解决方案(**/*.*)最为信息丰富且最有效。被接受的答案会导致顶级目录中的项目重复。我的工作模式是:"${path}"**/*.* - mummybot
尝试使用其他选项,如nullglob和dotglob会很有趣。 - Wilf

15

这将打印当前目录及其子目录中以'.ext'结尾的所有文件。

find . -name '*.ext' -print

尽管这个答案严格意义上不符合提问者所要求的“扩展”,但它很可能会产生预期的结果。 - Dennis Williamson

9
为什么不使用花括号扩展来包含当前目录呢?
./{*,**/*}.ext

花括号扩展发生在全局扩展之前,因此您可以在旧版本的bash中有效地执行所需操作,并可以放弃在新版本中使用globstar。

此外,在bash中,将./包含在全局模式中被认为是最佳实践。


4
$ find . -type f

这将列出当前目录中的所有文件。然后,您可以使用-exec在输出上执行其他命令。

$find . -type f -exec grep "foo" {} \;

那将从find命令返回的每个文件中搜索字符串“foo”。

现在已经过去了11年,也许是时候有人指出find . -type f递归地应用于当前目录的根目录,而不仅仅是当前目录了。 - Roger Dahl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接