行及びサイズ大のCSVをシンプル操作で分割しつつ各々にヘッダもつけてみた

CSVが想定外の行数及びサイズ過多により、ビューア等で開くことが困難になる状態はなるだけ避けたいものです。無理して開こうとした結果、応答がなくなり最悪PCを再起動する羽目になったこともありました。

解決策としては以下が挙げられます。

  • 物理的にマシンスペックを上げる
  • ソフトウェアベースで一定行数で分割する

コスト重視とした場合に、後者は可視性を上げるためにも分割したファイル各々へのCSVヘッダ付与を求められることがあります。「分割後のCSVに関してヘッダの有無は拘らない」のであれば問題ありませんが、往々にしてヘッダファイルとの結合を行うことになりやすいと思います。

そこで、CSVの分割とヘッダ結合について、手間を回避して負担を下げるべく幾つかの前提を考えました。

  • 複数行によるバッチ処理を経由しない
  • 実行する処理は実際に動作させなくても大体予想がつく
  • 枯れ切っていてメンテナンスの必要がない
  • 中間一時ファイルを必要としない

我ながら無茶苦茶だと思いましたが、これらを可能な限りカバーしつつ且つひと目見て大体分かる手段を模索検討してみました。

分割する方法

よくある手段として、

  • head
  • tail
  • split

辺りを利用することを考えましたが、検証し難くなります。csv系ライブラリの応用も考えましたが、ページングまでカバーしている都合のいいライブラリというものは中々見当たりませんでした。

stackoverflowで何か手頃な方法を検索してみたところ、以下のエントリーが出てきました。

手続きとしては見たことがない代物だったため、試しにparallelを元にCSVを対象にした記事を検索してみたところ、以下の記事にたどり着きました。

シンプルでかつわかりやすく、前提をキレイにカバーしてくれそうです。今回はこの方法で検証してみました。

ParallelでのCSV分割

恐らくインストール操作が必要だと思われます。

brew install parallel

そして、結果としては以下のコマンドとなりました。最大で50001行のCSVファイルが1つ以上できる形です。

cat input.csv | parallel --header : --pipe -N 50000 'cat > split-{#}.csv'

利用したオプションは以下の2つです

  • header
  • pipe

via: GNU Parallel manual

header

引数を指定しない場合には入力リソースの一行目を置換対象の文字列として扱います。今回は実質以下の通りです。

--header : is an alias for --header '.*\n'.

pipe

入力リソースを渡します。 -N 50000 の指定により、処理毎に元のCSVから50000行ずつ渡していきます。

今回のCSVの一行目には置換用の文字列が含まれていないため、処理毎に1行目にヘッダとその後50000行のCSVがそのまま渡されており、それらをまとめてcsvに出力しています。

まとめ

parallelのコマンドオプションが幅広いことと、使い慣れていないコマンドだったこともあり、想定している動作とズレがないかの検証が主となりました。

headerの使い方とpipeの指定がシンプルに収まったため、parallelに慣れるための一歩としても使いやすい題材だったと思います。

より深く踏み込んでみたい場合は、以下のようなスライドがあります。参考にどうぞ。