Bash star * 通配符是否总是产生(升序)排序列表?

56 shell bash wildcards

我有一个目录,里面装满了文件名logXX,其中 XX 是两个字符、零填充的大写十六进制数字,例如:

log00
log01
log02
...
log0A
log0B
log0C
...
log4E
log4F
log50
...
Run Code Online (Sandbox Code Playgroud)

一般来说,总共会有少于 20 或 30 个文件。我的特定系统上的日期和时间不是可以依赖的(没有可靠的 NTP 或 GPS 时间源的嵌入式系统)。然而,文件名将可靠地增加,如上所示。

我希望grep通过某种类型的单个最新日志条目的所有文件,我希望将cat这些文件放在一起,例如...

cat /tmp/logs/log* | grep 'WARNING 07 -' | tail -n1
Run Code Online (Sandbox Code Playgroud)

然而,它发生,我认为不同版本bashshzsh等可能有关于如何不同的想法*展开。

man bash页面没有说明 的扩展*是否是匹配文件名的绝对升序字母列表。每次我在所有可用的系统上尝试它时,它似乎都在上升——但它是定义的行为还是特定于实现的行为?

换句话说,我绝对可以依靠cat /tmp/logs/log*按字母顺序将所有日志文件连接在一起吗?

Sté*_*las 56

在所有 shell 中,默认情况下对 glob 进行排序。它们已经被/etc/globKen Thompson 的 shell 调用助手在 70 年代早期的第一个 Unix 版本中扩展了 globs(并且给了 globs 的名字)。

对于sh,POSIX 确实要求通过 对它们进行排序strcoll(),即使用用户语言环境中的排序顺序,ls尽管有些人仍然通过进行排序,但strcmp()仅基于字节值。

$ dash -c 'echo *'
Log01B log-0D log00 log01 log02 log0A log0B log0C log4E log4F log50 log? log? lóg01
$ bash -c 'echo *'
log? log? log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ zsh -c 'echo *'
log? log? log00 log01 lóg01 Log01B log02 log0A log0B log0C log-0D log4E log4F log50
$ ls
log?  log?  log00  log01  lóg01  Log01B  log02  log0A  log0B  log0C  log-0D  log4E  log4F  log50
$ ls | sort
log?
log?
log00
log01
lóg01
Log01B
log02
log0A
log0B
log0C
log-0D
log4E
log4F
log50
Run Code Online (Sandbox Code Playgroud)

您可能会注意到,对于那些基于语言环境进行排序的 shell,在具有语言环境的 GNU 系统上en_GB.UTF-8-文件名中的 被忽略以进行排序(大多数标点字符会)。该ó以更期望的方式(至少英国人)排序,并忽略大小写(当谈到决定关系除外)。

但是,您会注意到日志的一些不一致之处吗?日志?。那是因为 ? 和 ?没有在 GNU 语言环境中定义(目前;希望有一天会修复)。它们排序相同,因此您会得到随机结果。

更改语言环境会影响排序顺序。您可以将语言环境设置为 C 以获得类似strcmp()的排序:

$ bash -c 'echo *'
log? log? log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ bash -c 'LC_ALL=C; echo *'
Log01B log-0D log0.2 log00 log01 log02 log0A log0B log0C log4E log4F log50 log? log? lóg01
Run Code Online (Sandbox Code Playgroud)

请注意,即使对于 all-ASCII all-alnum 字符串,某些语言环境也会引起一些混淆。像捷克语一样(至少在 GNU 系统上),其中ch是排序后的整理元素h

$ LC_ALL=cs_CZ.UTF-8 bash -c 'echo *'
log0Ah log0Bh log0Dh log0Ch
Run Code Online (Sandbox Code Playgroud)

或者,正如@ninjalj 所指出的,在匈牙利语环境中甚至更奇怪:

$ LC_ALL=hu_HU.UTF-8 bash -c 'echo *'
logX LOGx LOGX logZ LOGz LOGZ logY LOGY LOGy
Run Code Online (Sandbox Code Playgroud)

在 中zsh,您可以选择使用glob 限定符进行排序。例如:

echo *(om) # to sort by modification time
echo *(oL) # to sort by size
echo *(On) # for a *reverse* sort by name
echo *(o+myfunction) # sort using a user-defined function
echo *(N)  # to NOT sort
echo *(n)  # sort by name, but numerically, and so on.
Run Code Online (Sandbox Code Playgroud)

echo *(n)也可以使用以下numericglobsort选项全局启用数字排序:

$ zsh -c 'echo *'
log? log? log00 log01 lóg01 Log01B log02 log0.2 log0A log0B log0C log-0D log4E log4F log50
$ zsh -o numericglobsort -c 'echo *'
log? log? log00 lóg01 Log01B log0.2 log0A log0B log0C log01 log02 log-0D log4E log4F log50
Run Code Online (Sandbox Code Playgroud)

如果您(就像我一样)对那个特定实例(这里使用我的英国语言环境)中的顺序感到困惑,请参阅此处了解详细信息。


use*_*274 36

bash 的手册页确实指定了:

路径名扩展

词的拆分之后,除非该-f选项已被设置,bash将扫描的文字每个字*?[。如果出现这些字符中的一个,则该单词被视为一个模式,并替换为按字母顺序排序的与模式匹配的文件名列表 [...]。

  • 你涵盖了`bash`。Tho OP 也对“zsh 等”感兴趣。 (3认同)

Kus*_*nda 28

除非您在某些 shell 中触发一些非常具体的 shell 选项,否则保证输出是相同的。

顺序在POSIX 标准中指定:

如果模式匹配任何现有的文件名或路径名,则模式应替换为这些文件名和路径名,根据当前语言环境中有效的整理顺序进行排序。如果此整理顺序没有对所有字符进行总排序(请参阅 XBD LC_COLLATE),则应使用 POSIX 语言环境的整理顺序进一步逐字节比较任何同等整理的文件名或路径名。

另请参阅POSIX Locale 中的 LC_COLLATE Category ,简而言之,如果LC_COLLATE=C,则事物按 ASCII 顺序排序。


bash手册中提到

LC_COLLATE

此变量确定在对路径名扩展的结果进行排序时使用的整理顺序,并确定路径名扩展和模式匹配中的范围表达式、等价类和整理序列的行为。

ksh93并且zsh有类似的措辞,这让我相信他们在这方面遵循 POSIX 标准。

其他外壳,例如pdksh并且dash没有说明由文件名通配产生的文件名的排序。我很想相信这意味着它们仍然遵循相同的标准,至少在使用 POSIX 语言环境时是这样。根据我的经验,我还没有遇到过对 ASCII 文件名进行任何明显“奇怪”排序的 shell。

  • 请参阅会影响排序的 `zsh` 中的 `numericglobsort` 选项。虽然我更愿意像 `echo *(n)` 那样在 per-glob 的基础上启用它,而不是全局启用该选项。 (2认同)