Glob 文件*不*使用外壳

Mig*_*ell 7 shell wildcards

我想列出某个子目录中的文件,但我这样做是作为docker execdocker 容器内部的一部分,所以我不想费心启动一个我并不真正需要的 shell。是否可以使用简单的命令行工具而不只是 shell 找到 glob 的所有匹配项?

例如,我当前的调用是bash -l -c 'echo /usr/local/conda-meta/*.json'. 是否可以使用常用的工具来简化这个过程,从而得到类似 的东西globber /usr/local/conda-meta/*.json,它会更简单、重量更轻?

Sté*_*las 16

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
Run Code Online (Sandbox Code Playgroud)

直接地。如果不:

sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'
Run Code Online (Sandbox Code Playgroud)

你也可以find在这里使用。find不进行 globbing,但它可以报告匹配类似于 shell 模式的文件名。

LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'
Run Code Online (Sandbox Code Playgroud)

或者使用一些find实现:

LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'
Run Code Online (Sandbox Code Playgroud)

(请注意,LC_ALL=C这里需要*匹配任何字节序列,而不仅仅是那些在当前语言环境中形成有效字符的字节序列,是一个 shell 构造。如果该命令行不被 shell 解释,您可能需要将其更改为env LC_ALL=C find...)

与 shell globs 的一些区别:

  • 文件列表未排序
  • 包含隐藏文件(您可以添加一个! -name '.*'以排除它们)
  • 如果没有匹配的文件,则不会得到任何输出。globs 有这样的错误特征,即在这种情况下它们保持模式未扩展。
  • 对于第一个(标准)变体,文件将输出为/usr/local/conda-meta/./file.json.
  • 一些 globs 之类x*/y/../*z的不容易翻译(还要注意在这种情况下与目录的符号链接有关的不同行为)。

在任何情况下,都不能用于echo输出任意数据。

我的下一个问题是:你打算用那个输出做什么?使用echo,您将输出由 SPC 字符分隔的文件路径,而使用 myprintffind更高版本,则输出由 NL 字符分隔的文件路径。这两个NLSPC是完全在文件名中的有效字符,所以这些输出是没有经过后处理的可靠。您可以使用'%s\0'代替'%s\n'(或使用find's-print0如果支持),不适合向用户显示,但可后处理。

在效率方面,将 Ubuntu 20.04 /bin/sh(破折号 0.5.10.2)与其find(GNU find4.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
Run Code Online (Sandbox Code Playgroud)

通配一些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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

当然,YMMV 取决于系统、实现、相应实用程序的版本以及它们所链接的库。

现在在历史记录中,glob名称实际上来自于glob70 年代初在 Unix 的第一个版本中调用的实用程序的名称。它位于/etc并被调用sh作为扩展通配符模式的助手。

您会在网上找到一些项目来恢复非常旧的 shell,例如https://etsh.nl/。更多地作为考古学练习,您可以glob从那里构建实用程序,然后能够执行以下操作:

glob printf '%s\n' '/usr/local/conda-meta/*.json'
Run Code Online (Sandbox Code Playgroud)

虽然有一些警告。

  • 那些是古老的球体,[!x](更不用说[^x])不受支持。
  • 它不是 8 位安全的。实际上,第 8 位用于转义glob 运算符($'\xe9*'将匹配与 相同的内容i*$'\xaa*'匹配以 开头的文件名*;shell 会在调用之前为引用的字符设置第 8 位glob
  • 范围如[a-f]匹配字节值而不是排序规则(实际上,这通常是 IMO 的优势)。
  • 不匹配的 glob 会导致No match错误(同样,可能最好是,这是在 70 年代后期被 Bourne shell破坏的东西)。

glob功能后来从 70 年代后期的 PWB shell 和 Bourne shell 开始移入 shell。后来,一些fnmatch()glob()函数被添加到 C 库中,以允许从其他应用程序中使用该功能,但我不知道标准或通用实用程序是该函数的裸接口。甚至perl用于csh在其早期调用来扩展全局模式。


Bas*_*tch 7

使用 shell 的Glob 文件

要阅读的明显文档是glob(7)

您可以编写或使用 C 程序调用fnmatch(3)glob(3)nftw(3)stat(2)readdir(3)

如果您使用GuilePythonGoRustOcamlCommon Lisp(例如SBCL编写代码……您会发现类似的函数。使用 C++ 查看POCOQt

我假设您使用的是 Linux 系统。顺便说一句,我的交互式 shell 是zsh(恕我直言,它的自动完成功能更可取)。

  • OP 还询问不使用 shell,这是要使用的工具,因此在没有 shell 工具可用的容器中恢复到系统库听起来是一个很好的建议。 (6认同)