レコードの集合を永続的に保存させ,いつでも取り出せ,さらに加工が できるようにするシステムをデータベース管理システム(DBMS)という。 実用されているDBMSはデータの保存,取り出し以外の豊富な機能を含む 巨大なシステムであるが,簡単なものはRubyプログラムで容易に実装できる。
テキストファイルの解析と書き出しはopenメソッドで行なう(既習範囲)。
csvを読んで加工し,csvに書き出す処理は, csvライブラリ を利用する。ただし遅いので(Ruby1.8) 実用性を求めるなら FasterCSVやcsvscanをインストールして使うことになる。
Unixシステムの多くはdbmという汎用的なデータベースライブラリを 備えている。簡易データベースはこれで十分作成できる。Rubyでは 添付ライブラリの dbm を介して簡単に利用できる。
dbmによるデータベースは Hash と同様 key と value の対の集合をデータとする。ただし,Hashと 違い,keyとvalueがともに文字列(String)でなければならない。
csvで書かれたデータを読み込み,どんどんdbmに追加する例を示す。 csvのレコードは第1フィールドがキーと見なせるとする(重複するものが ないと保証できる)。
#!/usr/bin/env ruby # coding: euc-jp require 'dbm' # dbmクラスを利用 datafile = "./database" # データベース名 # CSVデータを読んでハッシュにいれておく data = Hash.new while csv=gets # 第1フィールドをキー,残りをごっそり文字列として値にする if /([^,]*),(.*)/ =~ csv data[$1] = $2 end end # ndbm形式のデータベースを開く DBM.open(datafile) do |x| # 開いた時点で既にデータがあれば x に入った状態でスタートする for key, val in data # data の key, val ペアを取り出して繰り返す x[key] = val end end
走らせるとデータベース名に .dir .pag
を付加したファイルができる(BSDの場合は *.db
)。
市のデータファイル
と町のデータファイル,
村のデータファイルを順次処理した推移を観察しよう。
./dbm-add.rb yama-city.csv ls -l data* -rw-r--r-- 1 yuuji wheel 16384 Apr 27 11:35 database.db strings database.* | less (中を確認して q で終了) ./dbm-add.rb yama-town.csv ls -l data* strings database.db | less ./dbm-add.rb yama-vil.csv
strings
コマンドは何をするものか調べよ。
(n)dbm形式のデータファイルは makedbm -u
で
テキスト部分を抽出できる。
●Solarisの場合 makedbm -u database ●NetBSDの場合 makedbm -u database.db
作成したデータベースを読み込むプログラムも簡単で,
DBM.open
を使えばよい。
#!/usr/bin/env ruby # coding: euc-jp require 'dbm' # dbmクラスを利用 datafile = "./database" # データベースファイル名 # ndbm形式のデータベースを開いて順次レコードを出力 DBM.open(datafile, 0666, DBM::READER) do |x| # DBM.open(datafile) だけで開いてもよいが, # 読み込みだけのときは第3引数を DBM::READER とする for key, val in x # x の key, val ペアを取り出して繰り返す printf("%s -> %s\n", key, val) end end
データ追加,ダンプ,検索の機能を選べるようにした プログラム例を示す。
#!/usr/bin/env ruby # coding: euc-jp require 'dbm' # dbmクラスを利用 datafile = "./database" # データベースファイル名 if ARGV[0] == nil then STDERR.puts "#{$0} -a [Data.csv] Add record" STDERR.puts "#{$0} -d Dump database" STDERR.puts "#{$0} -s Search on database" exit 1 end case ARGV.shift when "-a" # データの追加登録 # CSVデータを読んでハッシュにいれておく data = Hash.new DBM.open(datafile) do |x| # この処理はあまりよくない(後述) while csv=gets # 第1フィールドをキー,残りをごっそり文字列として値にする if /([^,]*),(.*)/ =~ csv #data[$1] = $2 x[$1] = $2 end end end when "-d" # 一覧出力 DBM.open(datafile, 0666, DBM::READER) do |x| for key, val in x printf("%s -> %s\n", key, val) end end when "-s" # 検索 STDERR.print "検索キー: " kwd = STDIN.gets.chomp! # STDIN無しだと -s というファイルから読む reg = Regexp.new(kwd, nil, 'e') # 大文字小文字区別,EUCで検索 DBM.open(datafile, 0666, DBM::READER) do |x| for k in x.keys if reg =~ k then # マッチしたレコードのみ出力 printf("%s -> %s\n", k, x[k]) end end end end
dbm-ops.rb
を実行してみよ。
ただし,このプログラムの追加登録部分は潜在的な問題を含んでいる。
データのキー削除には delete メソッドを用いる。
db.delete(key)
とすると該当するキーと値を削除できる。
Ruby固有の汎用的なデータ永続化クラスが PStore で,ほぼ全てのオブジェクトがファイルに保存できる。
「商品」と「単価」のkey, valueペアを持つハッシュを永続的に 持ち,追加で増やせるプログラム例を示す。
#!/usr/bin/env ruby # coding: euc-jp require 'pstore' datafile = 'pricedb' def showall(hash) for k, v in hash printf("%-20s%4d円\n", k, v) end end db = PStore.new(datafile) db.transaction do price = db["price-list"] = db.fetch("price-list", Hash.new) while true STDERR.print "商品=単価 の形式で入れて下さい(C-dで終了): " break if (line=gets) == nil redo if /([^=]*)=([\d ]*)/ !~ line # マッチしなかったら redo price[$1] = $2.to_i end puts "\n全商品リストです" showall(price) end
プログラム中で指定している "price-list"
は,
ルート名といい,任意のルート名に対応する値を定義し,それを保存できる。
ひとつのpstoreファイルに複数のルートを持たすことができる。