読むだけでちょっと賢くなる、架空のまとめスレ

【疑問】sort の順番がマシンによって違う。LANG・LC_*・LC_ALL の優先順位と locale、文字コードと iconv【LPICレベル1】

24レスカテゴリ:Linux
1: 名無しさん

同じファイルを sort したのに、手元のマシンとサーバーで並び順が違った
中身は A、B、a、b、_c の5行

2: 名無しさん

前提のおさらい。環境変数はシェルから起動したコマンドに引き継がれて、ログインしたときに ~/.profile などの設定ファイルで決まる

3: 名無しさん

>>1
ロケール(言語と地域の設定)の違い。並び順は LC_COLLATE で決まる。試した結果
LANG=C:A B _c a b(バイトの値の順)
LANG=en_US.UTF-8:a A b B _c(辞書の順。大文字小文字を並べて、記号は後回し)

4: 名無しさん

>>3
ロケールって何を決めてるん

5: 名無しさん

>>4
カテゴリごとに分かれてる
LC_CTYPE 文字の種類と文字コード、LC_COLLATE 並び順、LC_MESSAGES メッセージの言語、LC_NUMERIC 数の書き方、LC_TIME 日付と時刻の書き方、LC_MONETARY 通貨
今の設定は locale で全部表示される

6: 名無しさん

>>5
数の書き方の違いも試した。printf "%'d\n" 1234567 が en_US.UTF-8 では 1,234,567、C では 1234567

7: 名無しさん

>>5
LANG と LC_ALL とか LC_COLLATE とか、全部設定されてたらどれが効くん

8: 名無しさん

>>7
LC_ALL がいちばん強くて、全部のカテゴリを上書きする。次が LC_COLLATE などの個別のもの、最後が LANG(ほかで決まってないカテゴリの既定値)
LC_ALL > LC_* > LANG

9: 名無しさん

>>8
試したら、LC_ALL=C と LC_COLLATE=en_US.UTF-8 を両方付けて sort すると C の順(A B _c a b)になった。LC_ALL が勝つ

10: 名無しさん

>>8
LC_ALL は普段の設定に使うもんやなくて、一時的に全部そろえたいときに使う

11: 名無しさん

スクリプトの先頭で LANG=C とか LC_ALL=C とか書いてあるのを見るけど、なんで?

12: 名無しさん

>>11
どのマシンで動かしても同じ結果にするため。sort の順番、grep の [a-z] の範囲、エラーメッセージの言語、小数点の記号などがロケールで変わる
エラーメッセージを grep で拾うスクリプトなんかは、メッセージが日本語になると動かなくなる

13: 名無しさん

>>12
コマンド1つだけなら LC_ALL=C sort file みたいに前に付ければ、そのコマンドにだけ効く

14: 名無しさん

>>4
ロケールの名前の ja_JP.UTF-8 ってどう読むん

15: 名無しさん

>>14
言語_地域.文字コード。ja は日本語、JP は日本、UTF-8 が文字コード。C(POSIX と同じ)は最小限の英語のロケール
使えるロケールは locale -a。試したマシンは C、C.utf8、POSIX の3つだけやった。Debian は /etc/locale.gen を編集して locale-gen(か dpkg-reconfigure locales)で作る

16: 名無しさん

>>15
システム全体の既定は Debian なら /etc/default/locale、Red Hat 系や systemd では /etc/locale.conf。localectl set-locale LANG=ja_JP.UTF-8 で変えられる(systemd が動いてないと使えない)

17: 名無しさん

>>15
文字コードの UTF-8、ISO-8859、ASCII の違いは?

18: 名無しさん

>>17
ASCII は7ビットで

文字。英数字と記号だけ
ISO-8859 は1文字1バイト(256通り)で、-1 が西ヨーロッパ、-5 がキリル文字のように地域ごとに分かれてる
UTF-8 は1文字1〜4バイトで、世界中の文字を扱える。ASCII の範囲はそのまま1バイト

19: 名無しさん

>>18
od で見たら、é は UTF-8 で c3 a9 の2バイト、ISO-8859-1 で e9 の1バイト。あ は UTF-8 で e3 81 82 の3バイト

20: 名無しさん

>>18
文字コードの変換は?

21: 名無しさん

>>20
iconv -f 元の文字コード -t 変換先の文字コード ファイル
iconv -f UTF-8 -t ISO-8859-1 a.txt > b.txt
変換先にない文字があると止まる。あ を ISO-8859-1 にしたら illegal input sequence at position 0。-t ASCII//TRANSLIT にすると é は e、あ は ? に置き換わった
使える文字コードの一覧は iconv -l

22: 名無しさん

>>21
Shift_JIS の古いファイルなら iconv -f SHIFT_JIS -t UTF-8。あ は Shift_JIS で 82 a0 やった

23: 名無しさん

>>15
試したマシンには en_US と ja_JP のロケールがなかったから、/workspace の下の作業用ディレクトリに localedef で作って、LOCPATH でそこを指して試した。システムのロケールは変えてない

24: 名無しさん

>>8
並び順は LC_COLLATE、強さは LC_ALL > LC_* > LANG、スクリプトは LC_ALL=C で固定、変換は iconv。サーバーとの違いはロケールやったわ

管理人のひとこと