テキストファイルをソートするときに頻繁に使うUnixコマンド

@nokuno さんの Unix コマンドに関する記事を読みました。

- テキストファイルを処理するときのUnixコマンドまとめ
-- http://d.hatena.ne.jp/nokuno/20120121/1327139192

実務では何でもかんでも難しい手法や複数台の計算機で処理することが正義ではなく「同じ処理を実現する最も簡単で効果的な手法」で処理することが正義なので、この手の記事はいくつあっても良いものだ、と思いました。

便乗して、僕が中小規模なデータをソートする際に頻繁に使っている Unix コマンド(と Perl のワンライナー)も書いてみます。


サンプルのTSVファイル


特に意味はないファイルですが2列目は値段です。どうでもいいですけど割と適正な価格が付いていると思います。カラムはタプ文字で区切っています。

■ test_imput.txt
cat test_imput.txt
コーヒー        270     E     B
ミルク  300     F     A
ココア  350     D     B
抹茶    350     C     A
オレンジジュース        300     B     B
ウーロン茶      270     A     A

以下ではこのファイルをソートしてみます。

列全体をキーにしてソート


sort するときは LC_ALL=C sort を使わないと、環境によって結果が変わってしまう場合があるし、一定量以上のファイルをsortする際には、sort処理自体の速度もLC_ALL=Cありの方が速いです。

LC_ALL=C sort < test_imput.txt
ウーロン茶     270     A     A
オレンジジュース     300     B     B
ココア     350     D     B
コーヒー     270     E     B
ミルク     300     F     A
抹茶     350     C     A


任意の列だけを比較して sort したい


上記は行ごとに比較をしているけど、以下のようにタブで区切った任意の値の値でソートすることもできます。もちろん値の境界文字も任意で指定できます。

3列目の値をキーにしてソート

LC_ALL=C sort -t $'\t' -k 3,3 < test_imput.txt 
ウーロン茶     270     A     A
オレンジジュース     300     B     B
抹茶     350     C     A
ココア     350     D     B
コーヒー     270     E     B
ミルク     300     F     A


2列目の値を一般的な数値のキーとしてソート

もちろん、以下のようにタブで区切った2列目の値の値でソートすることもできます。その時に-gオプションを付けると、一般的な数値として比較することができます。他にもsort方法のオプションはあるのでman sortを見てみましょう。

LC_ALL=C sort -t $'\t' -k 2,2 -n < test_imput.txt 
ウーロン茶     270     A     A
コーヒー     270     E     B
オレンジジュース     300     B     B
ミルク     300     F     A
ココア     350     D     B
抹茶     350     C     A


■man sortの結果の一部
       -g, --general-numeric-sort
              compare according to general numerical value

       -i, --ignore-nonprinting
              consider only printable characters

       -M, --month-sort
              compare (unknown) < `JAN' < ... < `DEC'

       -h, --human-numeric-sort
              compare human readable numbers (e.g., 2K 1G)

       -n, --numeric-sort
              compare according to string numerical value

       -R, --random-sort
              sort by random hash of keys


2〜3列目の値をキーにしてソート

さらに、2カラム目から3カラム目までを考慮することもできます。

LC_ALL=C sort -t $'\t' -k 2,3 < test_imput.txt
ウーロン茶     270     A     A
コーヒー     270     E     B
オレンジジュース     300     B     B
ミルク     300     F     A
抹茶     350     C     A
ココア     350     D     B


任意の列を任意の順に組み合わせたキーでソートしたい

ここで4列目と2列目を取り出して、sortのキーを「4列目2列目」としたい場合には、僕はいったんPerlスクリプトで別のファイルを生成したりしますが、Unix コマンドだけでもやればできます。

cutコマンドは各行から任意のカラムをカラム番号の昇順に列を抜き出します。今回は2より4を先にしたいのでcutコマンドを2回実行します。オプションがsortと違うのか紛らわしいです。

■2列目と4列目をそれぞれ切り出す
cut -d $'\t' -f 2 < test_imput.txt > test_imput_cut2.txt
cat test_imput_cut2.txt
270
300
350
350
300
270

cut -d $'\t' -f 4 < test_imput.txt > test_imput_cut4.txt
cat test_imput_cut4.txt 
B
A
B
A
B
A

ちなみにcutは複数のカラムを同時に指定できるのですが、上にも書いたとうり指定した番号を昇順にしてしまいます。そのため以下のようにしても、2列目4列目、と出力されるので無駄です。

cut -d $'\t' -f 4,2 < test_imput.txt
270     B
300     A
350     B
350     A
300     B
270     A

出力したら paste コマンドでマージします。

■切り出したファイルと元のファイルを結合
paste test_imput_cut4.txt  test_imput_cut2.txt test_imput.txt > test_imput_paste.txt
cat test_imput_paste.txt
B     270     コーヒー     270     E     B
A     300     ミルク     300     F     A
B     350     ココア     350     D     B
A     350     抹茶     350     C     A
B     300     オレンジジュース     300     B     B
A     270     ウーロン茶     270     A     A

最後に1列目と2列目でソートしたあと、3,4,5,6列目をcutで取り出します。

LC_ALL=C sort -t $'\t' -k 1,2 < test_imput_paste.txt > test_imput_paste_sort.txt
cat test_imput_paste_sort.txt
A     270     ウーロン茶     270     A     A
A     300     ミルク     300     F     A
A     350     抹茶     350     C     A
B     270     コーヒー     270     E     B
B     300     オレンジジュース     300     B     B
B     350     ココア     350     D     B

cut -d $'\t' -f 3,4,5,6 <  test_imput_paste_sort.txt
ウーロン茶     270     A     A
ミルク     300     F     A
抹茶     350     C     A
コーヒー     270     E     B
オレンジジュース     300     B     B
ココア     350     D     B

できましたね。

Perlのワンライナーを使って良いなら以下のようにもできます。

perl -ne '$l=$_;$l=~s|\n||;@a=split /\t/,$l;print "$a[3]\t$a[1]\t$l\n";' < test_imput.txt > test_imput_paste.txt
sort -t $'\t' -k 1,2  < test_imput_paste.txt > test_imput_paste_sort.txt
cut -d $'\t' -f 3,4,5,6 <  test_imput_paste_sort.txt
ウーロン茶     270     A     A
ミルク     300     F     A
抹茶     350     C     A
コーヒー     270     E     B
オレンジジュース     300     B     B
ココア     350     D     B

3行で済むのはとても楽ですね。

おわりに


大体の場合、こんな感じで整形をしたあと、任意のカラムを使ってキーに対する値を合計したり、キーをuniqにしたりします。

近いうちにキーの頻度カウント時によく使う Unix コマンドについて書こうと思います。


投稿者:としのり  日時:23:59:59 | コメント | トラックバック |
blog comments powered by Disqus