やりたいこと
サイズが大きいcsvは読みこむだけでも一苦労。だったら分割してやればいいと思ったわけですが、splitを使うと2ファイル目以降がヘッダ行が無くなって分からなくなってしまう。
そもそも、カラム数が多いcsvを頑張って開いて横スクロールすることに限界を感じたので、だったら横じゃなくて縦に斬ってやろう。
サンプル
- large.csvは15カラム目までがユニークなデータカラム、それ以降のカラムは51x51の繰り返しという、いかにもデータですというCSVです。
- 今回はこれをユニークなデータカラム、51x26のデータカラム51x25という具合に分割します(51x25も十分デカイですが...)
- それではサンプルのスクリプトです。
awkでcsvを縦に斬る
cat large.csv | awk -F"," 'BEGIN{OFS=","}{ret=""}{for(i=1;i<=NF;i++){if(i<16){if(ret==""){ret=$(i)}else{ret=ret","$(i)}}}}{print ret}' > small_1.csv
cat large.csv | awk -F"," 'BEGIN{OFS=","}{ret=""}{for(i=1;i<=NF;i++){if(i>15 && i < 51*26+16){if(ret==""){ret=$(i)}else{ret=ret","$(i)}}}}{print ret}' > small_2.csv
cat large.csv | awk -F"," 'BEGIN{OFS=","}{ret=""}{for(i=1;i<=NF;i++){if(i>51*26+15){if(ret==""){ret=$(i)}else{ret=ret","$(i)}}}}{print ret}' > small_03.csv
軽く説明
- まず
cat
で該当のCSVを読み込みパイプでawk
に流します -
awk
は-F","
でカンマを区切り文字にしています BEGIN
の最初の{OFS=","}
は意味をなしていませんが、CSVで出力したいという意思表示です。- for文で1~NF(awkの結果の取る値すべて)を回し、ifの条件の中で取り出したい範囲でを指定しretに格納します
- retに格納する際はretが空文字であれば$(i)を、retが空文字でなければカンマ区切りで結合
- 最後に
print ret
で出力して完了です。
課題
- パフォーマンスの問題
- まとめてて思ったのですが、for文ですべて回して、ifで範囲指定は無駄ですね。for文の中で範囲絞ったほうが速い。
- 3つに分割するのに3回ファイルを読み込むのは勿体無い。1回のループで3つの変数に格納して1つのファイルに出力して、splitで3分割するほうが速いのかもしれない。
結論
awkを使えばコマンドライン上でテキスト操作ができるので、使いこなせると便利。
そもそもこんなデカイCSVなんかつくらないでくれよ
追記
cutのほうが速いかもしれない、というコメントを頂きました。(ご指摘ありがとうございます。)
実際コマンドとしてはこんな感じですね
cutでCSVを縦に斬る
cut large.csv -f 1-15 -d ,
cut large.csv -f 16-1341 -d ,
cut large.csv -f 1342- -d ,
あとはこれをファイルに書きだしてやればOK、こちらのほうがコマンドがシンプルで使いやすいですね!!
あえてawkの利点をいうのであれば、csvのカラム数をNFで取得できるので、動的に分割するカラムを指定するスクリプトがかけるというところでしょうか。