我知道 **/*.ext
可以扩展到匹配所有子目录中的 *.ext
文件,但是有没有类似的扩展方式可以包括当前目录中的这些文件呢?
这将在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
时,输出行数几乎是两倍。
globstar
默认是关闭的。 - Zombo**/*.ext
应该足够了。另外,除非你使用 shopt -s dotglob
,否则你将无法获取隐藏的文件。 - gniourf_gniourfglobstar
:shopt -u globstar
。 - kenorb**/*.ext
不够用。 - msciwoj{*,**/*}
只匹配当前目录及其子目录,但不包括这些子目录的子目录。而且shopt
列表中没有globstar
选项。我已经尝试了homebrew提供的bash shell(4.4.23(1)),并开启了globstar选项,但它对我的glob模式也是一样的结果。 - dotnetCarpenter**/*.*
递归包含所有文件(启用方法: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
禁用)。这不是推荐的做法,因为它可能会影响像mv
或rm
这样的命令,并且您可能会意外地删除错误的文件。
**/*.*
)最为信息丰富且最有效。被接受的答案会导致顶级目录中的项目重复。我的工作模式是:"${path}"**/*.*
。 - mummybot这将打印当前目录及其子目录中以'.ext'结尾的所有文件。
find . -name '*.ext' -print
./{*,**/*}.ext
花括号扩展发生在全局扩展之前,因此您可以在旧版本的bash中有效地执行所需操作,并可以放弃在新版本中使用globstar。
此外,在bash中,将./
包含在全局模式中被认为是最佳实践。
$ find . -type f
这将列出当前目录中的所有文件。然后,您可以使用-exec在输出上执行其他命令。
$find . -type f -exec grep "foo" {} \;
find . -type f
递归地应用于当前目录的根目录,而不仅仅是当前目录了。 - Roger Dahl
**/*.ext
这个命令,你确定它可以在你的电脑上正常运行吗? - tangensglobstar
选项。 - kenorb