sortコマンドでCSVファイルをソートする場合はソート列の指定方法に注意

2019-05-20Bash

はじめに

sortコマンドを使ってソートを行うとき、「CSVファイル」や「TSVファイル」を対象とすることがあります。

ただし、意外と知られていないのがソートの条件として任意の列を指定する方法について。

-kオプションを使えば可能なのですがひと癖あるオプションなので取り上げました。

sortコマンドの挙動でハマった方の手助けになれば幸いです。

検証環境

$ uname -moi
x86_64 MacBookPro10,1 Darwin

$ bash -version
GNU bash, バージョン 5.0.7(1)-release (x86_64-apple-darwin18.5.0)

$ sort --version
sort (GNU coreutils) 8.31

準備

CSVをJSONに変換するためのNode.jsライブラリ「csv2json」 | ゲンゾウ用ポストイット でも使ったダミーデータ生成サービスを利用してCSVデータを取得します。

test.csv


# ヘッダー行は次のようになっています。(今回はさほど重要ではありません)
## 連番,氏名,氏名(カタカナ),性別,年齢,取得ポイント
$ cat test.csv
1,小出里歩,オデリホ,女,27,85
2,吉野里紗,ヨシノリサ,女,38,894
3,本郷末治,ホンゴウスエジ,男,56,252
4,谷村千代乃,タニムラチヨノ,女,44,556
5,内野響子,ウチノキョウコ,女,44,170
6,塩谷貢,シオタニミツグ,男,34,494
7,児島愛子,コジマアイコ,女,39,675
8,白木俊史,シラキトシフミ,男,57,245
9,飯塚遥佳,イイヅカハルカ,女,20,974
10,阿久津清蔵,アクツセイゾウ,男,9,120

整形すると以下のようなデータとなります。

$ cat test.csv | column -t -s,
1   小出里歩    オデリホ        女  27  85
2   吉野里紗    ヨシノリサ      女  38  894
3   本郷末治    ホンゴウスエジ  男  56  252
4   谷村千代乃  タニムラチヨノ  女  44  556
5   内野響子    ウチノキョウコ  女  44  170
6   塩谷貢      シオタニミツグ  男  34  494
7   児島愛子    コジマアイコ    女  39  675
8   白木俊史    シラキトシフミ  男  57  245
9   飯塚遥佳    イイヅカハルカ  女  20  974
10  阿久津清蔵  アクツセイゾウ  男  9   120

CSVやTSVのソートに使われるオプションは2つ

sortコマンドでCSVデータを取り扱う際には、「列の区切り文字」と「ソートに利用する列」を指定する必要があります。

  • 「列の区切り文字」は-tオプションを使って指定します。
  • 「ソートに利用する列」は、-kオプションを使って指定します。
    • 列番号を指定します。一番左の列は 1 として扱われます。

実際に試してみます。
わかりやすいように column -t -s, (CSVファイルを表形式に整形するコマンド)で整形して出力します。

# 7列目の「年齢」デソートします。
$ cat test.csv | sort -t, -k5 | column -t -s,
9   飯塚遥佳    イイヅカハルカ  女  20  974
1   小出里歩    オデリホ        女  27  85
6   塩谷貢      シオタニミツグ  男  34  494
2   吉野里紗    ヨシノリサ      女  38  894
7   児島愛子    コジマアイコ    女  39  675
5   内野響子    ウチノキョウコ  女  44  170
4   谷村千代乃  タニムラチヨノ  女  44  556
3   本郷末治    ホンゴウスエジ  男  56  252
8   白木俊史    シラキトシフミ  男  57  245
10  阿久津清蔵  アクツセイゾウ  男  9   120

気持ちの悪い点があります。 年齢順にソートしたつもりが正しくソートされていません(9才が一番下にある)

これを解消していきましょう。

数値列のソート時には数値であることを教えて上げる必要がある

「1. 年齢順にソートしたつもりが正しくソートされていない(9才が一番下にある)」 について。
sortコマンドは ソート列の値を一文字ずつ辞書順にソート します。
579 を比較した場合、一文字目の 59 では 5 のほうが小さいと判断されたわけです。

これを解消するためには -k5 オプションの後ろに n という値を付与して数値の大小比較をさせるように指定します。

# 先程は -k5 と指定していたオプションを -k5n に変更
# 今ひとつちゃんとソートされない。。。
$ cat test.csv | sort -t, -k5n | column -t -s,
1   小出里歩    オデリホ        女  27  85
10  阿久津清蔵  アクツセイゾウ  男  9   120
9   飯塚遥佳    イイヅカハルカ  女  20  974
6   塩谷貢      シオタニミツグ  男  34  494
2   吉野里紗    ヨシノリサ      女  38  894
7   児島愛子    コジマアイコ    女  39  675
5   内野響子    ウチノキョウコ  女  44  170
4   谷村千代乃  タニムラチヨノ  女  44  556
3   本郷末治    ホンゴウスエジ  男  56  252
8   白木俊史    シラキトシフミ  男  57  245

279 の値が正しくソートされていません。
なぜこのようなことが起こるのでしょうか?

実はこれ、 sort-k オプションのわかりにくいところなんですね。

sortコマンドの-kオプションは「単一のソート列」を指定するものではない!

manコマンドで sort コマンドのオプションに関して調べてみましょう。

       -k, --key=KEYDEF
              sort via a key; KEYDEF gives location and type

       KEYDEF  is  F[.C][OPTS][,F[.C][OPTS]]  for  start and stop position, where F is a field number and C a character position in the field; both are origin 1, and the stop position
       defaults to the line's end.  If neither -t nor -b is in effect, characters in a field are counted from the beginning of the preceding whitespace.  OPTS  is  one  or  more  sin-
       gle-letter  ordering  options  [bdfgiMhnRrV], which override global ordering options for that key.  If no key is given, use the entire line as the key.  Use --debug to diagnose
       incorrect key usage.

苦手な英語(^^;)を紐解くと以下のようなことがわかります。

  • -kオプションではフィールド番号を指定する」
  • カンマ区切りで2つの数値を指定できる
  • カンマの前は「ソートに利用する列」の開始列番号
  • カンマの後ろは「ソートに利用する列」の終了列番号
    • デフォルトは行末

つまり、以下のコマンドはいずれも同じ処理を行っていることになります。

  • cat test.csv | sort -t, -k5n | column -t -s,
  • cat test.csv | sort -t, -k5,6n | column -t -s,

先程の問題は5列目の「年齢」だけでなく、6列目の「取得ポイント」列までをつなげて1つの数値として扱い、ソートされてしまったようです。

sortコマンドの-kオプションで「単一のソート列」を指定するには

先程のコマンドは以下のようにソート条件開始列、ソート条件終了列の両方を指定して、5列目だけを扱うようにすれば解決します。

$ cat test.csv | sort -t, -k5,5n | column -t -s,
10  阿久津清蔵  アクツセイゾウ  男  9   120
9   飯塚遥佳    イイヅカハルカ  女  20  974
1   小出里歩    オデリホ        女  27  85
6   塩谷貢      シオタニミツグ  男  34  494
2   吉野里紗    ヨシノリサ      女  38  894
7   児島愛子    コジマアイコ    女  39  675
4   谷村千代乃  タニムラチヨノ  女  44  556
5   内野響子    ウチノキョウコ  女  44  170
3   本郷末治    ホンゴウスエジ  男  56  252
8   白木俊史    シラキトシフミ  男  57  245

-kオプションは複数指定可能

「年齢」が同じデータについては、さらに「取得ポイント」でソートしたいと思います。

44 才の方のデータが「取得ポイント」順に表示されるようにしたい場合は-kオプションを更に追加します。

$ cat test.csv | sort -t, -k5,5n -k6,6n | column -t -s,
10  阿久津清蔵  アクツセイゾウ  男  9   120
9   飯塚遥佳    イイヅカハルカ  女  20  974
1   小出里歩    オデリホ        女  27  85
6   塩谷貢      シオタニミツグ  男  34  494
2   吉野里紗    ヨシノリサ      女  38  894
7   児島愛子    コジマアイコ    女  39  675
5   内野響子    ウチノキョウコ  女  44  170
4   谷村千代乃  タニムラチヨノ  女  44  556
3   本郷末治    ホンゴウスエジ  男  56  252
8   白木俊史    シラキトシフミ  男  57  245

2019-05-20Bash