我想列出某个子目录中的文件,但我这样做是作为 docker 容器内部的一部分docker exec
,所以我不想费心启动一个我并不真正需要的 shell。是否可以使用简单的命令行工具(而不仅仅是 shell)找到 glob 的所有匹配项?
例如,我当前的调用是bash -l -c 'echo /usr/local/conda-meta/*.json'
.是否可以使用常用的工具来简化此操作,从而产生类似的东西globber /usr/local/conda-meta/*.json
,这会更简单且重量更轻?
答案1
sh
简单且普遍可用。sh
是被调用来解析许多语言中的命令行的工具system(cmdline)
。许多操作系统,包括一些 GNU 操作系统,已经停止使用bash
(GNU shell)来实现,sh
因为它变得过于臃肿,无法完成解析命令行和解释 POSIXsh
脚本这样简单的事情。
您的bash -l -c 'echo /usr/local/conda-meta/*.json'
命令行可能已被调用解释sh
。所以也许你可以这样做:
printf '%s\n' /usr/local/conda-meta/*.json
直接地。如果不:
sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'
您也可以find
在这里使用。find
不进行通配,但它可以报告与 shell 类似的模式匹配的文件名。
LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'
或者通过一些find
实现:
LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'
(请注意,LC_ALL=C
此处需要*
匹配任何字节序列,而不仅仅是那些在当前语言环境中形成有效字符的字节序列,这是一个 shell 构造。如果 shell 不解释该命令行,您可能需要将其更改为env LC_ALL=C find...
)
与 shell glob 的一些区别:
- 文件列表未排序
- 包含隐藏文件(您可以添加
! -name '.*'
来排除它们) - 如果没有匹配的文件,则不会得到任何输出。 glob 有一个错误特征,在这种情况下它们会按原样保留未扩展的模式。
- 使用第一个(标准)变体,文件将输出为
/usr/local/conda-meta/./file.json
. - 有些 glob
x*/y/../*z
不容易翻译(另请注意在这种情况下目录符号链接的不同行为)。
无论如何,你不能用来echo
输出任意数据。
我的下一个问题是:您将如何处理该输出?使用 时echo
,您将输出由 SPC 字符分隔的文件路径,而使用 myprintf
或find
以上时,则由 NL 字符分隔。NL
和都是SPC
文件名中完全有效的字符,因此这些输出在后处理过程中不可靠。您可以使用'%s\0'
代替'%s\n'
(或使用find
's-print0
如果支持),不适合向用户显示,但可进行后处理。
在效率方面,将 Ubuntu 20.04 /bin/sh
(dash 0.5.10.2)与其find
(GNU find
4.7.0)进行比较。
启动时间:
$ time (repeat 1000 sh -c '')
( repeat 1000; do; sh -c ''; done; ) 0.91s user 0.66s system 105% cpu 1.483 total
$ time (repeat 1000 find . -quit)
( repeat 1000; do; find . -quit; done; ) 1.35s user 1.25s system 103% cpu 2.507 total
通配一些json
文件:
$ TIMEFMT='%U user %S system %P cpu %*E total'
$ time (repeat 1000 sh -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
0.95s user 0.72s system 105% cpu 1.587 total
$ time (repeat 1000 find /usr/share/iso-codes/json -mindepth 1 -maxdepth 1 -name '*.json') > /dev/null
1.34s user 1.35s system 103% cpu 2.599 total
Evenbash
几乎不比find
这里慢:
$ time (repeat 1000 bash -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
1.53s user 1.36s system 102% cpu 2.808 total
当然,YMMV 取决于系统、实现、相应实用程序的版本以及它们所链接的库。
现在,在历史记录中,全局这个名字实际上来自glob
70 年代初 Unix 的第一个版本中调用的一个实用程序的名称。它位于/etc
并被调用sh
作为扩展通配符模式的助手。
您会在网上找到一些项目来复兴非常古老的 shell,例如https://etsh.nl/。更多地作为考古学的练习,您可以glob
从那里构建实用程序,然后能够执行以下操作:
glob printf '%s\n' '/usr/local/conda-meta/*.json'
不过有一些警告。
- 这些是古老的球体,
[!x]
(更不用说[^x]
)不受支持。 - 它不是 8 位安全的。实际上,第8位用于逃跑glob 运算符(
$'\xe9*'
将匹配与 相同的内容i*
,$'\xaa*'
将匹配以 开头的文件名*
;shell 会在调用之前为引用的字符设置第 8 位glob
) - 范围,例如
[a-f]
字节值匹配而不是排序规则(实际上,在我看来,这通常是一个优势)。 - 不匹配的 glob 会导致
No match
错误(再次,可能更好的是,这就是破碎的由 70 年代末的 Bourne shell 开发)。
glob
后来,从 70 年代末的 PWB shell 和 Bourne shell 开始,该功能被转移到 shell 中。后来,一些fnmatch()
函数glob()
被添加到 C 库中,以允许其他应用程序使用该功能,但我不知道有一个标准或通用实用程序是该函数的裸接口。甚至在早期perl
用于调用来扩展全局模式。csh