dbmopen() は、キーと値によって構成される簡易的なデータベースファイル data を開き、これを連想配列に関連付けます。第3引数には作成するファイルのパーミッションを指定します。以降、連想配列への代入や参照は、簡易データベースファイルへの設定や参照になります。dbmclose() は簡易データベースを閉じます。
dbmopen() により、連想配列 %xx とデータベースファイル data を関連付け、%xx に値を代入することにより、データベースに値を保存します。
dbmopen(%xx, "data", 0644); $xx{'NAME'} = "Tanaka"; $xx{'AGE'} = "26"; dbmclose(%xx);
データベースファイル data は、実際には data.dir と data.pag という 2つのファイルで保存されます。データベースファイルには、「NAME の値が Tanaka」、「AGE の値が 26」という情報がバイナリデータとして保存されています。
連想配列 %yy とデータベースファイルを関連付け、データベースの内容を読み出します。
dbmopen(%yy, "data", 0644); print "NAME = $yy{'NAME'}\n"; print "AGE = $yy{'AGE'}\n"; dbmclose(%yy);
実行結果は次のようになります。
NAME = Tanaka AGE = 26
Perl 5 では、dbmopen() の代わりに tie() がサポートされました。tie() は、連想配列と、任意のデータベースシステムを関連付けます。untie() は関連付けを終了します。データベースシステムはモジュールとして提供されます。データベースモジュールには SDBM、DB、NDBM、GDBM などいろいろなものがあります。
データベースモジュールのひとつとして、SDBM を用いた例を下記に示します。第1引数に連想配列名、第2引数にデータベースシステムの名前(SDBM の場合は "SDBM_File")、第3引数にはファイルの読み書きモード、第4引数にはデータファイルのパーミッションを指定します。O_CREAT はファイルが存在しなければ作成すること、O_RDWR は読み書き両用モードでオープンすることを意味します。
use SDBM_File; use Fcntl; tie(%xx, "SDBM_File", "data", O_CREAT | O_RDWR, 0644); $xx{'NAME'} = "Tanaka"; $xx{'AGE'} = 26; untie(%xx); tie(%xx, "SDBM_File", "data", O_RDONLY, 0644); print "NAME = $xx{'NAME'}\n"; print "AGE = $xx{'AGE'}\n"; untie(%xx);
実行結果は次のようになります。
NAME = Tanaka AGE = 26
CSV は Comma Separated Value の略で、下記のようにひとつひとつのデータがカンマ(,)で区切られた形式のデータファイルをいいます。Microsoft 社の Excel など、ほとんどすべての表計算ソフトがこの形式をサポートしているため、アプリケーション間のデータの受け渡しによく用いられています。
Tanaka,26,Tokyo Suzuki,32,Osaka Yamada,18,Yokohama
CSVファイルを読み込むには次のようにします。
open(IN, "data.csv"); # ファイルを開いて while (<IN>) { # 1行ずつ読み込み s/[\r\n]*$//; # 行末の改行を削除して @cols = split(/,/); # カンマ(,)で分割 foreach $col (@cols) { # それぞれの項目を print "[$col] "; # print で表示 } print "\n"; # 1行毎に改行をいれて } close(IN); # ファイルをクローズ
例えば、上記のスクリプトで次のようなデータファイル(data.csv)を読み込むと、
Tanaka,26,Tokyo Suzuki,32,Osaka Yamada,18,Yokohama
結果は次のようになります。
[Tanaka] [26] [Tokyo] [Suzuki] [32] [Osaka] [Yamada] [18] [Yokohama]
CSVファイルを読み込んでこれを変数の値として保持する場合、次のようなサブルーチンを定義しておくと便利です。
sub ReadCsvFile { local($file, *data) = @_; local(*IN, $line, @cols, $ref); @data = (); open(IN, $file) || return 0; while ($line = <IN>) { $line =~ s/[\r\n]*$//; # 改行を取り除く @cols = split(/,/, $line); # カンマで分解する $ref = [ @cols ]; # 無名配列へのリファレンスを生成 push(@data, $ref); # リファレンスを配列に追加 } close(IN); return 1; }
このサブルーチンは次のようにして呼び出します。第1引数には CSVファイル名、第2引数には結果を受け取る配列変数へのリファレンスを渡します。
ReadCsvFile("data.csv", \@data) || die "読み込み失敗\n";
読み取った変数は、無名配列 へのリファレンスになります。次のようにして、その値を参照します。
foreach $ref (@data) { # それぞれの無名配列のリファレンスについて foreach $col (@$ref) { # リファレンスを配列にデリファレンスして、 print "[$col] "; # その要素を表示 } print "\n"; }
下記のように、1行目に項目名が記述されたCSVファイルの読み込みを行います。
NAME,AGE,ADDRESS Tanaka,26,Tokyo Suzuki,32,Osaka Yamada,18,Yokohama
これを読み込むには、前節で紹介した ReadCsvFile() を次のように改造します。
sub ReadCsvFile2 { local($file, *data, *items) = @_; local(*IN, $lineno, $line, @cols, $ref, $i); @data = (); @items = (); $lineno = 0; open(IN, $file) || return 0; while ($line = <IN>) { $line =~ s/[\r\n]*$//; @cols = split(/,/, $line); $lineno++; if ($lineno == 1) { # 1行目は項目名とみなして @items = @cols; # @items に格納する next; } $ref = {}; # 無名の連想配列へのリファレンスを作成 for ($i = 0; $i <= $#items; $i++) { $ref->{$items[$i]} = $cols[$i]; # 各値を代入 } push(@data, $ref); # リファレンスの配列に追加 } close(IN); return 1; }
呼び出しは次のようになります。第1引数は CSVファイル名、第2引数はデータの受け取り用、第3引数は項目名の受け取り用です。
ReadCsvFile2("data.csv", \@data, \@items) || die "読み込み失敗\n";
読み込んだデータを参照するには次のようにします。
foreach $ref (@data) { while (($name, $value) = each(%$ref)) { # デリファレンス print "[$name=$value] "; # 値を表示 } print "\n"; }
次のように参照することも可能です。
print "$data[1]->{'NAME'}\n";
こうして読み込んだ @data に 1行分のデータを追加するには次のようにします。
$ref = { 'NAME' => "Kudou", "AGE" => 45, "ADDRESS" => "Nagoya" }; push(@data, $ref);
@data から 1行分のデータを削除するには、splice() を用います。
splice(@data, 5, 3);
項目名は @items に格納されています。
foreach $item (@items) { print "$item "; } print "\n";
前節の ReadCsvFile() で読み込んだデータをCSVファイルに保存するには次のようにします。
sub SaveCsvFile { local($file, *data) = @_; local(*OUT, $ref, @cols, $item); open(OUT, "> $file") || return 0; foreach $ref (@data) { print OUT join(",", @$ref) . "\n"; } close(OUT); return 1; } SaveCsvFile("data2.csv", \@data) || die "書き込み失敗\n";
同様に ReadCsvFile2() で読み込んだ項目名付きデータをCSVファイルに保存するには次のようにします。
sub SaveCsvFile2 { local($file, *data, *items) = @_; local(*OUT, $ref, @cols, $item); open(OUT, "> $file") || return 0; print OUT join(",", @items) . "\n"; foreach $ref (@data) { @cols = (); foreach $item (@items) { push(@cols, $ref->{$item}); } print OUT join(",", @cols) . "\n"; } close(OUT); return 1; } SaveCsvFile2("data2.csv", \@data, \@items) || die "書き込み失敗\n";
同じ CSVファイルでも、カンマ(,)、ダブルクォーテーション(")、改行などを含むデータの保存方法がアプリケーションによって異なります。例えば、Microsoft 社の Excel では、次のような法則に従っているようです。日本語を扱う場合は漢字コードをシフトJISにしてください。
通常はデータをカンマ(,)で区切って保存します。改行コードは \r\n です。
AAAA BBBB CCCC → AAAA,BBBB,CCCC\r\n
データ中にカンマ(,)を含む場合は、データをダブルクォーテーション(")で囲みます。
AAAA BB,BB CCCC → AAAA,"BB,BB",CCCC\r\n
データ中にダブルクォーテーション(")を含む場合は、" を "" に変換してデータを "..." で囲みます。
AAAA BB"BB CCCC → AAAA,"BB""BB",CCCC\r\n
データ中に改行を含む場合は、改行を \n で表し、データを "..." で囲みます。\n はデータ中の改行、\r\n は行の改行になります。
AAAA BB改行BB CCCC → AAAA,"BB\nBB",CCCC\r\n
データ中にカンマ(,)、ダブルクォーテーション(")、改行を含む場合は、これらの組み合わせになります。
AAAA BB,"改行BB CCCC → AAAA,"BB,""\nBB",CCCC\r\n
前出の ReadCsvFile() や ReadCsvFile2() を Excel 形式のカンマ(,)やダブルクォーテーション(")に対応させるには、両サブルーチンの中に出てくる
open(IN, $file) || return 0; while ($line = <IN>) { $line =~ s/[\r\n]*$//; @cols = split(/,/, $line); : : }
という部分を次のように書き換えます。
open(IN, $file) || return 0; while ($line = <IN>) { $line =~ s/[\r\n]*$//; @cols = SplitAsExcelTypeCsvFormat($line); # Excel形式でカンマ分割 : : }
データ中の改行コードにも対応させるには、次のように改造します。
open(IN, $file) || return 0; binmode(IN); # バイナリモードにする while ($line .= <IN>) { # 次の行を $line にアペンドする if ($line !~ /\r\n$/) { next; } # 行末が \n なら次の行も読み込む $line =~ s/[\r\n]*$//; @cols = SplitAsExcelTypeCsvFormat($line); # Excel形式でカンマ分割 $line = ""; # $line をクリア : : }
SplitAsExcelTypeCsvFormat() サブルーチンの実装は次のようになります。
sub SplitAsExcelTypeCsvFormat { local($line) = @_; local($len) = length($line); # 1行の長さ local($n) = 0; # 列番号 local($qcount) = 0; # ダブルクォートの数 local(@cols) = (); # カラムデータ local($i, $c); for ($i = 0; $i < $len; $i++) { $c = substr($line, $i, 1); # 1文字取り出す if ($c eq ',') { if ($qcount == 0) { # ○○, の終わり $n++; } elsif ($qcount == 1) { # "○,○" の途中 $cols[$n] .= $c; } elsif ($qcount == 2) { # "○○", の終わり $n++; $qcount = 0; } } elsif ($c eq '"') { if ($qcount == 0) { # "○○" の始まり $qcount = 1; } elsif ($qcount == 1) { # "○○" の終わりかも $qcount = 2; } elsif ($qcount == 2) { # "○""○" の "" 部分 $cols[$n] .= $c; $qcount = 1; } } else { # 通常データ $cols[$n] .= $c; } } return @cols; }
ダブルクォーテーション(")の個数($qcount)をカウントすることにより、データ中のカンマ(,)やダブルクォーテーション(")に対応しています。
前出の SaveCsvFile() や SaveCsvFile2() を Excel 形式の CSV に対応させるには、両サブルーチンの中に出てくる
print OUT join(",", @cols) . "\n";
の部分を下記のように改造します。
print OUT JoinAsExcelTypeCsvFormat(@cols);
JoinAsExcelTypeCsvFormat() の実装は下記のようになります。
sub JoinAsExcelTypeCsvFormat { local(@cols) = @_; local($col, $line); $line = ""; foreach $col (@cols) { if ($col =~ /[",\n]/) { $col =~ s/"/""/g; # " を "" に置換 $col =~ s/\r\n/\n/g; # 念のため \r\n を \n に置換 $col = "\"$col\""; # "..." で囲む } if ($line eq "") { $line = $col; } else { $line .= ",$col"; } } $line .= "\n"; return $line; }