コマンドラインでCSVをソートする方法まとめ

結合テスト後の結果比較や生データの処理などのために、数MBくらいのCSVを取り扱いたい時がある。なかでも、いろいろな処理を行う前に、たいていtimestamp等で各行をソートする必要があることが多い。エンジニアになる前はExcelを開いて頑張っていたけれど、もっと手軽に処理したいと思うようになったので、Excelを開かずにコマンドでソートを行う方法をまとめてみた。

sortコマンドで直接ソート

特定の列の値でソートしたいだけなら、sortコマンドで直接できる。

sort -t, -k 3 file.csv

-t オプションは区切り文字の指定ができ、-k オプションは区切り文字を基準とした列数を指定できる。このため、上の例ではカンマ区切りのファイル(=CSV)のファイルを、3列目の値でソートできる。もちろん、ソートの基準に関わる他のオプション(-f, -n等)も併用できる。

特定の列の値のみを抽出してソート

上記のsortコマンドでファイル全体をソートするのが最も単純なやり方なのだが、例えば新旧のテスト結果の中で、特定の列の値について比較したいときもある。しかしdiffを使ってファイル同士を比較すると、行単位でしか差分が表示されないため、1行のデータが多いとどこが異なるのか見づらい。そんなときは、特定の列のデータを取り出すと比較しやすくなる。
特定の列を抽出するやり方はいくつかあるが、個人的にはawkコマンドが最も楽だった。

awk '{FS=","; print $3}' file.csv | sort

FSで入力データの区切り文字を指定できる。そしてその後に続くprint文で3列目を出力するようにしている。これをパイプでつなげばsortも可能になる。

また、例えば「6列目のフラグが1だったもののみ3列目のタイムスタンプでソートしたい」というようなこともできる。

awk '{FS=","; if($6 == 1){print $3}}' file.csv | sort

awkコマンドはAWK言語のインタプリタ処理系プログラムなので、この他にもいろいろなことが柔軟にできるらしい。少し興味が出てきた。

列内の一部の文字列でソート

もっと複雑なCSVファイルになると、JSON文字列が一列に書き込まれていることもある。ソートに使いたいのは一部の文字だけなんだが... こんなときはsedコマンドの置換機能を使うようにしている。

sed 's/^.*,([0-9]{32}),.*$/\1/p' file.csv | sort

正規表現の中で()を使うと、その部分を後で指定することができる。この場合、抽出したい文字の部分のみを()で囲み、後ろで\1と指定することで、正規表現にマッチして置換が起きた場合に、欲しい文字のみが残るようにしている。そして最後の\pで置換した部分のみを出力するように指定することで、任意の部分の文字列抽出が可能になる。
ただし、欲しい部分にマッチする正規表現を書くのがなかなか大変。上の例の場合、例えば数字32桁の列が他にもあるとうまく抜き出せない。また、sedコマンドの正規表現はnon-greedyな表記に対応していないのも辛いところ。その場合は、多少無理やりな正規表現になることを覚悟して頑張るようにしているが、便利な回避策があったら知りたい。

コマンドの世界は奥が深いので、他にも便利なやり方がありそう。本を読んで勉強するより「これできないかな?」と思ったときに調べる方が身につきそうなので、まずはすぐに気合と根性に逃げてしまう習慣をなくしたい。