rcmdnk's blog

Numbers 2013 - Sort

ある端末で使っていたlssortを使ったスクリプトを 他の端末で動かしたらどうも思ったとおりの結果にならなくて、 調べた結果LC_ALL等の設定が違っていてソート結果が違ってしまったようでした。

問題が起こりやすいケース

問題がよく起こるのはファイル名を付けるとき、 最初のファイルには数字など付けずに、 2つ目以降には_1.txt等、アンダーバーを付けて数字を付けるケース。

aaa.txt
aaa_1.txt
aaa_2.txt
...

みたいな。

自分で気をつけていてもソフトによってこの様なファアイル名の付け方を 勝手にするものもあるのでファイルとして持ってることはあると思います。

ロケールによる違い

lssortコマンド等で一覧をソートする場合、 環境設定によって順序が変わってくる事があります。

test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
a1
a_1
a-1
a,1
a.1
a2
a_2
a-2
a,2
a.2
A1
A_1
A-1
A,1
A.1
A2
A_2
A-2
A,2
A.2

こんな感じのファイルを用意してsortしてみます。 行ってる環境はLinuxです。

ロケールをCにしてやってみます。

$ LC_ALL=C sort test.txt
A,1
A-1
A.1
A1
AA
A_1
a,1
a-1
a.1
a1
a_1
aa

まず記号の,-.が来て、次に数字文字と来て 最後に_が来ます。

また、大文字が先で小文字が後です。

これをUTFなロケールにしてみると

$ LC_ALL=en_US.UTF-8 sort test.txt
a1
a_1
a-1
a,1
a.1
A1
A_1
A-1
A,1
A.1
aa
AA

こんな感じになります。

まず、小文字が最初に来ています。

ただ、第一文字で全てソートされず、aaと文字列が続くものは 最初が小文字でも第二文字が記号や数字のものより下に来ています。

数字、記号でのソートは 数字_-,.の順。

Cでは最後に来ていた_が数字の次に来ています。

._の順序が前後しているので 最初に書いた問題が起こりやすいケースの _1.txtの様に数字をファイル名に付ける要な場合、 lsなりsortなりで順序を整えようとすると

  • LC_ALL=C: aaa.txt, aaa_1.txt, aaa_2.txt

Cではaaa.txtが最初に来るのに対し

  • LC_ALL=en_US.UTF-8: aaa_1.txt, aaa_2.txt, aaa.txt

en_US.UTF-8では最後に来てしまいます。

もし、読み込む順序が意味あるものであれば 後者の方では間違った結果を得る可能性があります。

sortのマニュアルを見てみると

** WARNING ** The locale specified by the environment affects sort order. Set LC_ALL=C to get the traditional sort order that uses native byte values.

とあります。

この様な記号が入ってくる可能性のあるsortや 順番を気にする時に使うlsなんかでは

LC_ALL=C sort test.txt

の様にロケールを必ずCにして実行する様にすべきだ、ということです。

ロケール設定

上ではLC_ALLを設定していますが、 これ以外にもロケールを設定する値があります。

sort等で使われるのは LC_CORRATELANGです。

まず、LC_ALLを見て、設定されてない場合、LC_CORRATELANGと見ていきます。

LC_ALLに関しては、これを設定すると 全てのLC_XXXの値を上書きします。

LC_ALLを設定した後にLC_CORRATE等を設定しても LC_ALLに上書きされます。

ロケール設定を見るにはlocaleというコマンドを使います。

$ env | grep LC_
$ env | grep LANG
$

LC_XXXLANGが何も設定されてない状態で見てみると

$ locale
LANG=
LC_CTYPE="POSIX"
LC_NUMERIC="POSIX"
LC_TIME="POSIX"
LC_COLLATE="POSIX"
LC_MONETARY="POSIX"
LC_MESSAGES="POSIX"
LC_PAPER="POSIX"
LC_NAME="POSIX"
LC_ADDRESS="POSIX"
LC_TELEPHONE="POSIX"
LC_MEASUREMENT="POSIX"
LC_IDENTIFICATION="POSIX"
LC_ALL=
$

このPOSIXロケールは大概の場合Cと同じもので、 Unixの基本的な設定になります。

ここでLANGを設定してみると

$ export LANG=en_US.UTF-8
$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
...
LC_ALL=
$

と言った感じでLC_ALL以外のLC_の値がLANGと同じになります。

LC_の値を設定してみると

$ export LC_CTYPE=C
$ locale
LANG=en_US.UTF-8
LC_CTYPE=C
LC_NUMERIC="en_US.UTF-8"
...
LC_ALL=
$

と、設定したものだけ変更されます。

また、直接設定されたものは"が付きません。 設定時に付けても

$ export LC_CTYPE="C"
$ locale
LANG=en_US.UTF-8
LC_CTYPE=C
LC_NUMERIC="en_US.UTF-8"
...
LC_ALL=
$

同じ。

ここで、LC_ALLを設定すると

$ export LC_ALL=ja_JP
$ locale
LANG=en_US.UTF-8
LC_CTYPE="ja_JP"
LC_NUMERIC="ja_JP"
...
LC_ALL=ja_JP
$

と、LC_XXXは全てLC_ALLと同じになります。

それぞれを設定しなおしても

$ export LC_CTYPE=C
$ locale
LANG=en_US.UTF-8
LC_CTYPE="ja_JP"
LC_NUMERIC="ja_JP"
...
LC_ALL=ja_JP
$

と変更されません。

LC_XXXで設定を変えたい場合にはunset LC_ALLとして LC_ALLを削除する必要があります。

Mac (BSD)では

Macでも同じ様にロケールを変更してソートをしてみましたが 何に変更しても全て上のCにした結果と同じになりました。

Macに入ってるsortコマンドがBSD用だったりして違うのかな、と思って見てみたところ、

$ /usr/bin/sort
...

Report bugs to <[email protected]>.

と、どうもGNUのsortと同じ物を使ってる様子。

manとか見る限りでは全く一緒です。

linux - Equivalent of gnu sort -R on OSX? - Super User

terminal - How to replace Mac OS X utilities with GNU core utilities? - Ask Different

この辺を見る限り、少なくとも以前まではBSD用のsort 1 を使ってた模様。

ただ、 sort(1) : FreeBSD 一般コマンドマニュアル: http://freebsd4-jman.kandk.co.jp/1/sort.1.html とかでもGNUのsortを説明していたりするのでBSD系全体として sortはGNUの物へ、となって来たのでしょうか。。。?

で、話がずれましたが、今回の話では逆に 同じsortを使っているがLinuxとMacで挙動が違う、と言う話です。

なのでちょっと追いきれてませんが、ロケール設定ファイルの方の 違いがあるのだと思います。

参考:

sort - Why does ls sorting ignore non-alphanumeric characters? - Unix & Linux Stack Exchange

環境変数LC_ALLは未定義のほうがよい?ロケール用環境変数について - 試験運用中なLinux備忘録

Sponsored Links
Sponsored Links

« Windowsがスリープに入らなかったり勝手に復帰したりする時に原因を調べる ここ最近ブログでよくコピーされる物 »

}