与えられたデータを自動的に処理するプログラムの必要性と有用性は高い。 そのことを見越して、自動処理しやすい形式でデータファイルを 作成しておくことが重要である。 また,Webページのアクセスログなど,計算機が自動的に生成する ログファイルは,テキスト形式で書き出されるものが多い。
テキストを処理するプログラムは,その入力パターンに応じて処理を 決定する。入力パターンの照合は対象となる書式を元に決める。
split
メソッドで分解
正規表現を用いて解析
ある特定の文字でフィールドを区切られている書式(DSV; Delimiter-Separated
Value)で,フィールド値に区切り文字が現れないようなものは
split
で簡単に解析できる。CSVファイルの処理例を示す。
yamagata.csv
(※)は,
文字列もクォートなしで書かれたcsvファイルで,
※ e-Stat 政府統計の総合窓口 http://www.e-stat.go.jp/SG1/estat/List.do?bid=000001034995cycode=0 表番2. 男女別人口及び世帯の種類(2区分)別世帯数 都道府県,市部,郡部, 市町村・旧市町村 より抜粋
第6フィールド | 市町村名 |
第9フィールド | 総人口 |
第10フィールド | 男人口 |
第11フィールド | 女人口 |
となっている。
7,大項目,地域コード,地域識別コード,境域年次(2010),境域年次(2000),,人口 総数,人口 男,人口 女,世帯数 総数,世帯数 一般世帯,世帯数 施設等の世帯 11,,6201,2,2010,2000,山形市,254244,121433,132811,96560,96425,135 12,,6202,2,2010,2000,米沢市,89401,43953,45448,33013,32920,93 13,,6203,2,2010,,鶴岡市,136623,64846,71777,45514,45395,119 20,,6204,2,2010,,酒田市,111151,52610,58541,38955,38860,95 25,,6205,2,2010,2000,新庄市,38850,18432,20418,12980,12958,22 26,,6206,2,2010,2000,寒河江市,42373,20497,21876,12717,12702,15 27,,6207,2,2010,2000,上山市,33836,16036,17800,10751,10709,42 28,,6208,2,2010,2000,村山市,26811,12846,13965,7865,7860,5 29,,6209,2,2010,2000,長井市,29473,14211,15262,9269,9228,41 30,,6210,2,2010,2000,天童市,62214,30148,32066,20404,20338,66 31,,6211,2,2010,2000,東根市,46414,22934,23480,14388,14343,45 32,,6212,2,2010,2000,尾花沢市,18955,9138,9817,5332,5320,12 33,,6213,2,2010,2000,南陽市,33658,16025,17633,10567,10538,29
これを split
で分解して,
市町村と総人口のみの集計出力を得るには以下のようにする。
#!/usr/bin/env ruby # -*- coding: utf-8 -*- while line=gets data = line.chomp.split(",") if /\d+/ =~ data[7] # 第8フィールドが数字の連続なら printf("%s,%s\n", data[6], data[7]) end end
実行すると以下のようになる。
./split-yg.rb yamagata.csv
山形市,254244
米沢市,89401
鶴岡市,136623
酒田市,111151
新庄市,38850
寒河江市,42373
上山市,33836
村山市,26811
長井市,29473
天童市,62214
東根市,46414
尾花沢市,18955
南陽市,33658
このように,CSVやタブ文字区切りなどで明確にフィールド分けされた テキストファイルはsplitメソッドで必要なフィールドを抽出できる。
ただし,CSVファイルでも文字列をダブルクォートで括ったり, なおかつその内部にさらにカンマやダブルクォートが含まれるようなものは splitで単純にフィールド分けできない。たとえば以下のようなCSVファイルが該当する。
番号,氏名,ローマ字,所属 1,公益太郎,"KOEKI, Taro",野球部 2,飯森花子,"IIMORI, Hanako",茶道部 3,三川一二三,"MIKAWA, Hifumi",SKIP
クォートを含むCSVを読んで加工し,CSVに書き出す処理は, csvライブラリ を利用する。これを利用して上記のCSVファイルをフィールドごとに分解してみる。
#!/usr/bin/env ruby # -*- coding: utf-8 -*- require 'csv' # csvライブラリの利用 CSV.foreach("quoted.csv") do |row| puts row.join("|") # 縦棒(|)でフィールドを区切って出力 end
フィールド値にカンマが含まれていてもクォートされているため 一つのフィールド値として処理されていることが以下の実行例から分かる。
./csv-split.rb
番号|氏名|ローマ字|所属
1|公益太郎|KOEKI, Taro|野球部
2|飯森花子|IIMORI, Hanako|茶道部
3|三川一二三|MIKAWA, Hifumi|SKIP
固定区切りでないものは正規表現でパターンマッチを行ない, 後方参照を用いてマッチした一部を取り出す。
以下のような電子メイルのファイルから差出人を抽出してみる。
Return-Path: <ta01002@e.koeki-u.ac.jp> Delivered-To: yuuji@itl.koeki-u.ac.jp Received: (qmail 12673 invoked by uid 1010); 26 Apr 2014 22:52:17 -0000 Received: (qmail 12659 invoked from network); 26 Apr 2014 22:52:17 -0000 Received: from broy.e.koeki-u.ac.jp (HELO localhost) (172.17.54.112) by pan.e.koeki-u.ac.jp with SMTP; 26 Apr 2014 22:52:17 -0000 Received: from broy.e.koeki-u.ac.jp (HELO localhost) (172.17.54.112) by pan.e.koeki-u.ac.jp (antibadmail 1.38) with SMTP; Apr 27 07:52:17 JST 2014 Date: Mon, 27 Apr 2014 07:52:10 +0900 (JST) Message-Id: <20140427.075210.193698131.ta01002@e.koeki-u.ac.jp> To: yuuji@e.koeki-u.ac.jp Subject: Nice to meet you From: HIROSE Yuuji <ta01002@e.koeki-u.ac.jp> Mime-Version: 1.0 Content-Type: Text/Plain; charset=iso-2022-jp Content-Transfer-Encoding: 7bit Status: はじめまして、こんにちは、 では、さようなら。 おしまい。 -- 公益太郎
差出人はメイルヘッダの Return-path
か,From
の値から取得する。電子メイルのヘッダは,1行目から始まり,空行で終わる。
行頭(先頭カラム)から始まり,コロンまでのものがフィールド名,
その後ろがフィールド値である。
メイルのヘッダ部分のみを繰り返す処理は,
while line=gets break if /^$/ =~ line # 行頭(^)の直後に行末($)で空行を表すパターン 〜処理〜 end
のように書ける。この間で,Return-path
または
From
を得る処理は以下のようにする。
while line=gets break if /^$/ =~ line if /^(return-path|from): *(.*)/i =~ then from = $1 end end
これを応用して,1通のメッセージを含むファイルから差出人などの 情報をサマリ表示するプログラムを作ると以下のようになる。
#!/usr/bin/env ruby # coding: euc-jp # Parse rfc5322 header and display summary while line=gets break if /^$/ =~ line if /^(return-path|from): *(.*)/i =~ line then from = $2 elsif /^subject: (.*)/i =~ line then subj = $1 elsif /^date: (.*)/i =~ line then date = $1 end end printf("%s: %sさんからの「%s」というメイルです。\n", date, from, subj)
この例では,Return-Path と From に加え,サブジェクトを保持する Subject ヘッダと,日付けを保持する Date ヘッダの値をそれぞれ取得している。
プログラムで集計処理した結果を仮想端末画面に出すようなものの場合, 出力項目ごとに桁揃えした形式にすると読みやすくなる。たとえば CSV ファイルの出力例として,
./csv-split.rb
番号|氏名|ローマ字|所属
1|公益太郎|KOEKI, Taro|野球部
2|飯森花子|IIMORI, Hanako|茶道部
のようなものがあったが,以下のようにすると見やすくなる。
./csv-split.rb
番号 | |氏名 | |ローマ字 | |所属 |
1 | |公益太郎 | |KOEKI, Taro | |野球部 |
2 | |飯森花子 | |IIMORI, Hanako | |茶道部 |
3 | |三川一二三 | |MIKAWA, Hifumi | |SKIP |
このような場合,printfのフォーマット指定で桁幅を指定すればよい。 各フィールドの桁幅を以下のように見積もる。
第1フィールド | 4桁右寄せ |
第2フィールド | 16桁左寄せ |
第3フィールド | 25桁左寄せ |
第4フィールド | 指定なし(残り桁すべて) |
これを表現するprintfフォーマット指定は以下のようになる。
printf("%4s|%-16s|%-25s|%s\n", *row)
csv-split.rb
の出力部分を上のprintfに書き換える。
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'csv' # csvライブラリの利用
CSV.foreach("quoted.csv") do |row|
printf("%4s|%-16s|%-25s|%s\n", *row)
end
ところがこれを実行すると以下のような結果が得られる。
番号|氏名 |ローマ字 |所属 1|公益太郎 |KOEKI, Taro |野球部 2|飯森花子 |IIMORI, Hanako |茶道部 3|三川一二三 |MIKAWA, Hifumi |SKIP
これは,%s では日本語1字が1とカウントされるのに対し, 実際に表示される桁幅が2であるため,表示幅計算がずれるためである。
Ruby1.8以前でeuc-jpやshift_jisコードを用いた場合は, 日本語1字の幅が2と計算されたためずれることなく表示された。 Ruby1.9以降やutf-8を利用する場合で,日本語でのきれいな桁揃えを 行ないたい場合は,文字列を一度euc-jpのような2バイトコードに変換し, なおかつバイト文字列とRubyに見なさせてフォーマットを行なわせ, 出力のときに元の文字コードに戻すようにするとよい。 具体的には以下のプログラムを利用する。
class String require 'kconv' if defined?("".force_encoding) def toeucbin() self.toeuc.force_encoding("binary") end else def toeucbin() self.toeuc end end end class IO def printf(*args) out = sprintf(*(args.collect{|x| x.is_a?(String) ? x.toeucbin : x })) print out.toutf8 end end class Object def printf(*args) if args[0].is_a?(String) $stdout.printf(*args) else port = args.shift port.printf(*args) end end end
日本語桁揃え処理をしたいプログラムの冒頭で,
上記 kprintf.rb
を読み込むように変えると
printf
の桁揃えがうまくいく。
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'csv' # csvライブラリの利用
require './kprintf.rb' # 日本語桁幅揃え(この行を追加)
CSV.foreach("quoted.csv") do |row|
printf("%4s|%-16s|%-25s|%s\n", *row)
end
実行すると以下のように桁が揃う(等幅フォントの場合)。
./csv-kprintf.rb
番号 | |氏名 | |ローマ字 | |所属 |
1 | |公益太郎 | |KOEKI, Taro | |野球部 |
2 | |飯森花子 | |IIMORI, Hanako | |茶道部 |
3 | |三川一二三 | |MIKAWA, Hifumi | |SKIP |
本講では,日本語を含む整形出力処理が必要なプログラムでは
この kprintf.rb
を読み込むようにする。