Bash のスター * ワイルドカードは常に (昇順) にソートされたリストを生成しますか?

Bash のスター * ワイルドカードは常に (昇順) にソートされたリストを生成しますか?

次のような名前のファイルでいっぱいのディレクトリがあります。XXlogXXは、次のような 2 文字のゼロ埋めされた大文字の 16 進数です。

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

通常、ファイルの合計数は 20 または 30 未満になります。私の特定のシステムの日付と時刻は信頼できるものではありません (信頼できる NTP または GPS 時間ソースのない組み込みシステム)。ただし、ファイル名は上記のように確実に増加します。

grep特定のタイプの最新のログ エントリのファイルをすべて調べたいのですが、cat次のようなファイルをまとめて調べたいと考えています...

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

bashしかし、またはshまたはなどの異なるバージョンでは、がどのように展開されるzshかについて異なる考え方があるかもしれないことに気付きました。*

このman bashページには、 の展開が、*一致するファイル名の昇順のアルファベット順リストになるかどうかは書かれていません。私が利用できるすべてのシステムで試したところ、毎回昇順になっているように見えますが、これは定義された動作なのでしょうか、それとも実装固有のものでしょうか?

言い換えれば、cat /tmp/logs/log*すべてのログ ファイルをアルファベット順に連結することに絶対的に頼ることができるのでしょうか?

答え1

すべてのシェルでは、glob はデフォルトでソートされます。彼らはすでに/etc/globヘルパーのそばにいた70 年代初頭の Unix の最初のバージョンで、Ken Thompson のシェルによって glob を展開するために呼び出されました (glob の名前の由来です)。

の場合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

上記で、ロケールに基づいてソートを行うシェルの場合、en_GB.UTF-8ロケールのある GNU システムでは、-ファイル名の はソート時に無視されることに気付いたかもしれません (ほとんどの句読点は無視されます)。 はó、より期待される方法でソートされ (少なくとも英国人にとっては)、大文字と小文字は無視されます (同点の場合を除く)。

しかし、log① log② には矛盾があることに気づくでしょう。これは、① と ② のソート順が GNU ロケールで定義されていないためです (現時点では、いつか修正されることを期待しています)。これらは同じようにソートされるため、ランダムな結果が得られます。

ロケールを変更すると、ソート順が変わります。ロケールを C に設定すると、strcmp()-like ソートが実現します。

$ 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

chいくつかのロケールでは、すべてASCIIのすべて数値の文字列であっても混乱が生じる可能性があることに注意してください。チェコ語のロケール(少なくともGNUシステムでは)では、照合要素並べ替え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、並べ替え方法を選択できます。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.

数値ソートは、以下のオプション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

いくつかのシェルで非常に特殊なシェル オプションをトリガーしない限り、出力は同じになることが保証されます。

順序はPOSIX標準:

パターンが既存のファイル名またはパス名と一致する場合、パターンはそれらのファイル名とパス名に置き換えられます。現在のロケールで有効な照合順序に従ってソートされますこの照合順序にすべての文字の完全な順序がない場合 (XBD LC_COLLATE を参照)、照合結果が等しいファイル名またはパス名は、POSIX ロケールの照合順序を使用してバイトごとにさらに比較する必要があります。

参照POSIX ロケールの LC_COLLATE カテゴリつまり、 の場合LC_COLLATE=C、物事は ASCII 順に並べられるということです。


マニュアルbashにはこう書かれている

LC_COLLATE

この変数は、パス名拡張の結果をソートするときに使用する照合順序を決定し、パス名拡張とパターン マッチング内の範囲式、同値クラス、および照合シーケンスの動作を決定します。

ksh93zsh同様の文言があり、この点では POSIX 標準に準拠していると考えられます。

や などの他のシェルでは、pdkshファイルdash名のグロブによるファイル名のソートについては何も言及されていません。これは、少なくとも POSIX ロケールを使用する場合は、同じ標準に準拠していることを意味すると信じたくなります。私の経験では、ASCII ファイル名の明らかに「奇妙な」ソートを行うシェルに出会ったことはありません。

答え4

主な目的が、入力ファイルを古い順に並べ替えることである場合、次のように記述できます。

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

回転および圧縮されたログも関係する場合は次のようになります。

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

関連情報