Bash 星号 * 通配符是否总是生成(升序)排序列表?

Bash 星号 * 通配符是否总是生成(升序)排序列表?

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

log00
log01
log02
...
log0A
log0B
log0C
...
log4E
log4F
log50
...

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

我希望grep遍历特定类型的单个最新日志条目的所有文件,我希望将cat这些文件放在一起,例如......

cat /tmp/logs/log* | grep 'WARNING 07 -' | tail -n1

然而,我想到不同版本的bashshzsh可能对如何*扩展有不同的想法。

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

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

答案1

在所有 shell 中,glob 都是默认排序的。他们已经在/etc/glob助手身边了由 Ken Thompson 的 shell 调用,用于在 70 年代初的 Unix 第一个版本中扩展 glob(glob 也因此得名)。

对于sh,POSIX 确实要求它们通过 进行排序strcoll(),即使用用户区域设置中的排序顺序,就像 for 一样,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

您可能会注意到,对于那些根据语言环境进行排序的 shell,在具有en_GB.UTF-8语言环境的 GNU 系统上,-文件名中的 会被忽略进行排序(大多数标点符号都会)。以更预期的方式排序ó(至少对英国人来说),并且忽略大小写(除非涉及决定关系)。

但是,您会注意到 log① 和 log② 存在一些不一致之处。这是因为 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

请注意,即使对于全 ASCII all-alnum 字符串,某些语言环境也可能会导致一些混乱。就像捷克语一样(至少在 GNU 系统上),哪里ch整理元素排序之后h

$ LC_ALL=cs_CZ.UTF-8 bash -c 'echo *'
log0Ah log0Bh log0Dh log0Ch

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

$ LC_ALL=hu_HU.UTF-8 bash -c 'echo *'
logX LOGx LOGX logZ LOGz LOGZ logY LOGY LOGy

在 中zsh,您可以选择排序全局限定符。例如:

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.

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

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

答案2

bash 的手册页确实指定了:

路径名扩展

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

答案3

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

顺序指定于POSIX 标准

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

也可以看看POSIX 语言环境中的 LC_COLLATE 类别,简而言之,如果LC_COLLATE=C,则事物按 ASCII 顺序排序。


说明书bash上提到

LC_COLLATE

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

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

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

答案4

如果主要目标是按输入文件的年龄对输入文件进行排序,最旧的在前,您可以编写

(cd /tmp/logs; cat `ls -rt log*`) | grep whatever

如果还涉及旋转和压缩日志:

(cd /tmp/logs; zcat -f `ls -rt log*`) | grep whatever

相关内容