macOSのソートがアホで困る
これは SmartHR Advent Calendar 2024 シリーズ2 の2日目の記事です。
ちょっと前にツイートしたのがまあまあ見られたみたいなので、ちゃんと書いてみる(汚い言葉は良くないね)。
macOS で次のテキストファイルを sort コマンドでソートするとデタラメに並ぶんですよ。
千葉県千葉市
千葉県市川市
千葉県柏市
東京都北区
東京都千代田区
東京都大田区
東京都江戸川区
東京都江東区
東京都港区
$ sort a.txt
千葉県柏市
東京都北区
東京都港区
千葉県千葉市
千葉県市川市
東京都大田区
東京都江東区
東京都千代田区
東京都江戸川区
文字のコードを無視して文字数だけで並んでるように見える。使い物にならない。macOS アホすぎる。
原因は /usr/share/locale/ja_JP.UTF-8/LC_COLLATE
ファイルが la_LN.US-ASCII/LC_COLLATE
にリンクされてるからっぽい。
$ cd /usr/share/locale/
$ ls -l ja_JP.UTF-8/LC_COLLATE
lrwxr-xr-x 1 root wheel 28 10 15 20:22 ja_JP.UTF-8/LC_COLLATE -> ../la_LN.US-ASCII/LC_COLLATE
LC_COLLATE
は文字の照合規則を決めるもので、それが ASCII 文字のためのファイルを指してるということで、ASCII 文字以外は全部同値としてみなしてるということらしい。
つまりこういうデータをソートしてるのと同じ。そりゃ文字長順に並ぶわな。同じ文字とみなされた場合は文字コードの順に並ぶみたいだけども。
������
������
�����
�����
�������
������
�������
������
�����
なお LC_COLLATE=C
を指定することで単純なバイト列としてソートされるので、文字コード順にソートされる。
$ LC_COLLATE=C sort a.txt
千葉県千葉市
千葉県市川市
千葉県柏市
東京都北区
東京都千代田区
東京都大田区
東京都江戸川区
東京都江東区
東京都港区
もともとこれに気がついたのは sort コマンドではなくて PostgreSQL の ORDER BY
でのソートがおかしくなったからだった。
会社の開発用の PC は MacBook なんだけど、PostgreSQL は Docker を使って Linux 上で動かしてる。そのせいでかなり遅いんで、Docker 使わずに macOS 上で直接動かそうとしたらソート順がおかしくなった。
PostgreSQL のロケールは何もしないと libc が使われるので、OS 環境に依存する。
$ initdb -U postgres /tmp/hoge
postgres=# \x
拡張表示は on です。
postgres=# \l postgres
データベース一覧
-[ RECORD 1 ]--------+----------------
名前 | postgres
所有者 | masahiro.tomita
エンコーディング | UTF8
ロケールプロバイダー | libc
照合順序 | ja_JP.UTF-8
Ctype(変換演算子) | ja_JP.UTF-8
ICUロケール |
ICUルール: |
アクセス権限 |
この状態で ORDER BY すると OS と同じアホな子になってることがわかる。
postgres=# create table test (c varchar);
CREATE TABLE
postgres=# insert into test (c) values ('あああああ'),('いいいい'),('ううう'),('ええ'),('お');
INSERT 0 5
postgres=# select * from test;
c
------------
あああああ
いいいい
ううう
ええ
お
(5 行)
postgres=# select * from test order by c;
c
------------
お
ええ
ううう
いいいい
あああああ
(5 行)
RDB の Collation による妙な挙動で MySQL の寿司ビール問題を思い出したわ。
sort でやったのと同じように initidb 時に --lc-collate=C
を指定したらうまくいった。
$ initdb -U postgres --lc-collate=C /tmp/hoge
postgres=# \l postgres
データベース一覧
-[ RECORD 1 ]--------+------------
名前 | postgres
所有者 | postgres
エンコーディング | UTF8
ロケールプロバイダー | libc
照合順序 | C
Ctype(変換演算子) | ja_JP.UTF-8
ICUロケール |
ICUルール: |
アクセス権限 |
postgres=# select * from test order by c;
c
------------
あああああ
いいいい
ううう
ええ
お
(5 行)
しかし、エンコーディングがUTF-8 なのに COLLATE が C というのはどうなんだろうなー…と思って、PostgreSQL は libc ロケール以外にも ICU ロケールが使えるので、それを指定することにした。
$ initdb -U postgres --locale-provider=icu --icu-locale=ja_JP.UTF-8 /tmp/hoge
postgres=# \l postgres
データベース一覧
-[ RECORD 1 ]--------+------------
名前 | postgres
所有者 | postgres
エンコーディング | UTF8
ロケールプロバイダー | icu
照合順序 | ja_JP.UTF-8
Ctype(変換演算子) | ja_JP.UTF-8
ICUロケール | ja-JP
ICUルール: |
アクセス権限 |
postgres=# select * from test order by c;
c
------------
あああああ
いいいい
ううう
ええ
お
(5 行)
しかし macOS は UI が使いにくいとは思っていたが OS としてもダメだったとはなぁ。
いやまあ macOS なんて使わずに Docker コンテナ使えばまともな Linux が使えるんだけど、あまりにも遅いんで…。
ていうか、開発にデスクトップ Linux 使いたい。
Discussion