csvデータなどテキストベースのデータの塊の処理を必要とする仕事が時々まわってくる。 大抵は、検索系なので、grepしたあと、少し手作業で加工するだけ。しかし、たまに複数のデータファイルを特定のキーで結びつけたりしなければならなくなる。 今までは、リレーショナル・データベースにぶち込んでSQLで処理していた。が、先日なんでもUnixコマンドでやりましょう的な記事を見つけて、大変可能性を感じたので、やってみることにした。
用意したのは、以下の2つのCSVファイル。
やりたいことは、sample-地域.csvの県名とsample-ナンバー.csvの出身地を結びつけ、両方の情報を表示すること。SQLで言うjoin
$ cat sample-地域.csv ID,地域,県名, 1001,九州,宮崎, 1002,九州,鹿児島, 1003,九州,福岡, 1004,中国,山口, ,,, ,,, ,,,
$ cat sample-メンバー.csv ID,名前,性別,歳,出身地,,,,,, 1001,山田,男,50,福岡,,,,,, 1002,田中,女,32,宮崎,,,,,, 1003,中山,女,18,福岡,,,,,, 1004,佐藤,女,19,大分,,,,,, 1005,佐藤,男,48,山口,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, (続く) ,,,,,,,,,, ,,,,,,,,,,
sort , awk, sed そして join
joinコマンドを使うことが今回の目的だ。joinコマンドを使う上で重要なことは、対象ファイルがキーでソートされていること。その下準備のために、sort, awk, sedのコマンドを利用するのだ。
実行したコマンド行は、3行。 実行内容だけ解説します。各コマンドの詳細は helpやmanを見てください。
$ sed -e "/^[,I]/d" sample-メンバー.csv | sort -t , -k 5 | awk -F , '{print $1,$5,$2,$3,$4}' > member.txt $ sed -e "/^[,I]/d" sample-地域.csv | sort -t , -k 3 | awk -F "," '{print $1,$3,$2}' > area.txt $ join -j 2 area.txt member.txt
1行目のコマンドライン
sedは、stream editorの略。-e
は、"コマンド"を指定するオプション、/^[,I]/d
のdは削除(delete)のd、正規表現/^[,I]/
に一致する行を削除することを意味する。
sortは、名前の通り並び替え。-t ,
でデータが ","で仕切られていることを指定し、-k 5
で5番目の項目が並べ替えの基準どすることを指示する。
awkは、万能レコード加工ツールかな。色々出来るらしいので、もはや言語と言って良いかも。{}で囲まれたコマンドを実行する。-F ,
はデータのデリミタ(仕切り)が","であることを指定し、{}の中で、項目の並べ替えを行っている。$nが項目のIDだ。 以上のパイプ "|"でつなぎ、仕事をするのが、Unix流だ(そうだ)。
3行目のコマンドライン
3行目のjoinコマンドは、今回のポイント。-j 2
は、項目の2番目がジョインするためのキーであることを指定している。キーの指定は、各ファイル個別に出来るが、やはり細かいところはmanを見てください。
要約
1行目で、sample-メンバー.csvファイルを 1) 不要な行を削除し、2) キーでソートし、3) 行毎に整形しspace区切りのファイル member.txtを作る。
2行目で、同様に sample-地域.csvから、area.txtをつくる。
3行目で、2つのファイル area.txt member.txt をジョインする。
作成結果、出力結果は、以下の通り。CodeRider:sample baker$ cat area.txt 1001 宮崎 九州 1004 山口 中国 1003 福岡 九州 1002 鹿児島 九州 CodeRider:sample baker$ cat member.txt 1004 大分 佐藤 女 19 1002 宮崎 田中 女 32 1005 山口 佐藤 男 48 1001 福岡 山田 男 50 1003 福岡 中山 女 18 CodeRider:sample baker$ join -j 2 area.txt member.txt 宮崎 1001 九州 1002 田中 女 32 山口 1004 中国 1005 佐藤 男 48 福岡 1003 九州 1001 山田 男 50 福岡 1003 九州 1003 中山 女 18
rubyでやってみる
これと、ほぼ同様なことをrubyでやってみた。
総当たりでレコードを調べ、キー値が一致していたら、標準出力に出力すると言う結構乱暴なプログラム。
require 'ostruct' require 'csv' area = OpenStruct.new area.filename = "sample-地域.csv" area.key = 3 - 1 member = OpenStruct.new member.filename = "sample-メンバー.csv" member.key = 5 - 1 CSV::open(area.filename,"r") do |a| line_a = a.compact next if line_a.size == 0 CSV::open(member.filename,"r") do |b| line_b = b.compact next if line_b.size == 0 $stdout.print [line_a + line_b].join(","), "\n" if a[area.key] === b[member.key] end end
実行結果は、
$ ruby another_one.rb 1001,九州,宮崎,1002,田中,女,32,宮崎 1003,九州,福岡,1001,山田,男,50,福岡 1003,九州,福岡,1003,中山,女,18,福岡 1004,中国,山口,1005,佐藤,男,48,山口
OpenStructを使ったのは、JavaScriptのように自由にプロパティが設定出来て、整理しやすかったから。(使ってみたかったとかではない :-p)
もっと短くできるかもしれないけど、まあサッとつくるプログラムとしてはこんなもんでよいのでは。
Unixコマンドとrubyとどちらが便利だろうか? また、リレーショナル・データベースにぶち込んでSQLで処理すると言う手もあるが。
(余談) Numbersはすごい
余談だが、サンプルは Appleの表計算ソフト Numbersで作成した。Excelと違って、一寸面白い。表計算ソフトではなく、表プレゼンテーションソフトと言ったほうが良い。 あまり使っていなかったのでビックリした。気づいたことは
- シートと表は異なる
- シートは、ExcelのWorksheetとは全く違う。むしろBookにちかい
- 表には、一つの表を描くのが正しい
- 表は、シート内で複数配置することが出来、それぞれがExcelのworkseetのような機能をもつ
- その構造の御陰で、他の表のレイアウト(例えば幅とか)に影響されず、別の表を作成できる
- シートは、表(オブジェクト)を配置する台紙の役割。これがレポート用紙になる
この構造は、慣れると大変便利だ。Excelで文書を書くのは愚の骨頂だと常々思うが、Numbersならば、ページレイアウトを自然に出来るだろう。
ただ、Excelのように使うと問題ある。今回の場合、CSVへの出力だ。
- 表毎にCSVを出力出来るが、無駄な行や列を作成しておくと、その分 ,, が生成される。(取り除かなければならない)
- 行末の処理が、ちょっとおかしい(行末項目のソートが出来ないなど)。CSVを作成する場合は、行末に,を付けるように出力するか、後で補完したほうが良いようだ。
0 件のコメント:
コメントを投稿