created: 2024-09-05T03:41:28.224Z
macOS の sort コマンドはマルチバイト文字をきちんと扱えない
人生ではじめて日本語の stackoverflow で質問したものの顛末。
ソートがおかしい
macOS で ↓ の 3 行を LC_COLLATE=ja_JP.UTF-8
でソートすると予期せぬ結果になる。
入力
$ LC_ALL=ja_JP.UTF-8 sort --ignore-nonprinting <<__TXT__
なにわ男子の逆転男子
アニメ おじゃる丸
アニメ「烏は主を選ばない」
__TXT__
ソート結果
アニメ おじゃる丸
なにわ男子の逆転男子
アニメ「烏は主を選ばない」
アニメ
というカタカナの prefix をもっている 2 行の間に、なにわ
というひらがなの行が入っている。
ソートなので先頭の部分が同じもので並べられて欲しい。
実行環境
$ sort --version
2.3-Apple (165.100.8)
$ locale | grep -E '(COLLATE|ALL)'
LC_COLLATE="ja_JP.UTF-8"
LC_ALL=
もちろん LC_COLLATE=C
とするとこのソート順は再現しない。
$ LC_ALL=C sort --ignore-nonprinting <<__TXT__
なにわ男子の逆転男子
アニメ おじゃる丸
アニメ「烏は主を選ばない」
__TXT__
なにわ男子の逆転男子
アニメ「烏は主を選ばない」
アニメ おじゃる丸
原因
この不可解な動作は sort コマンドのバグではなくきちんと原因がある。
macOS のデフォルトの比較ルールの設定は LC_COLLATE=ja_JP.UTF-8
となっている。
しかし、なんと ja_JP.UTF-8
という指定は la_LN.US-ASCII
のエイリアスになっているらしい。
$ ls -l /usr/share/locale/ja_JP.UTF-8/LC_COLLATE
lrwxr-xr-x 1 root wheel 28 5 7 16:01 /usr/share/locale/ja_JP.UTF-8/LC_COLLATE -> ../la_LN.US-ASCII/LC_COLLATE
la_LN.US-ASCII
の比較ルールはこのようになっている。
- マルチバイト文字はすべて比較時に等価な扱いになっている
- ascii をマルチバイト文字と比較すると、ascii のほうが先でマルチバイト文字が後になる
参考
この種明かしはこれらのコメントとブログのおかげでたどり着くことができた。
- コメント | command line - macOS の sort コマンドでカタカナとひらがなのソート順が崩れる原因は? - スタック・オーバーフロー
- The macOS LC_COLLATE hunt - Zhiming Wang
変なソートがどう発生しているか
たとえば、あざとくて何が悪いの
と ふるカフェ系 ハルさんの休日
を比較するとき、内部的にはこのようになっているという例をあげる。
LC_ALL=ja_JP.UTF-8 sort --ignore-nonprinting <<__TXT__
あざとくて何が悪いの
ふるカフェ系!ハルさんの休日
__TXT__
期待としては あ
のほうが先に来るはずだが、ふ
のほうが先としてソートされる。
ふるカフェ系!ハルさんの休日
あざとくて何が悪いの
ステップごとに追うと
5 文字目までは両方ともマルチバイト文字。
マルチバイト文字は等価
というルールなので、ここまでだとどちらも同じ順位。
ふるカフェ系
あざとくて何
6 文字目は !
と が
の比較となる。
ふるカフェ系!
あざとくて何が
ascii 文字のほうが先でマルチバイト文字
というルールが適用される。
!
は ascii で が
はマルチバイト文字なので、ここで勝負がつき ふるカフェ系!
のほうが先だとされる。
ふるカフェ系!ハルさんの休日
あざとくて何が悪いの
これどうする
マルチバイト文字はすべて比較時に等価な扱いになっている
というルールはかなり不便なのでなんとかしたい。
.bashrc で export LC_COLLATE=C
することも考えたが、予期せぬところでハマったらイヤなので sort コマンドを使う時だけ照合ルールを変更したい。
alias を設定して、ユニコードの順番でソートされるようにした。
alias sort="LC_COLLATE=C sort"