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

相關內容