簡易データベース
■ 簡易データベースを用いる(dbmopen, dbmclose)
◆ 簡易データベース
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
■ xDBMを用いる(xDBM)
◆ xDBMとは
Perl 5 では、dbmopen() の代わりに tie() がサポートされました。tie() は、連想配列と、任意のデータベースシステムを関連付けます。untie() は関連付けを終了します。データベースシステムはモジュールとして提供されます。データベースモジュールには SDBM、DB、NDBM、GDBM などいろいろなものがあります。
◆ SDBM を用いた例
データベースモジュールのひとつとして、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ファイルを読みこむ
◆ CSVファイルとは
CSV は Comma Separated Value の略で、下記のようにひとつひとつのデータがカンマ(,)で区切られた形式のデータファイルをいいます。Microsoft 社の Excel など、ほとんどすべての表計算ソフトがこの形式をサポートしているため、アプリケーション間のデータの受け渡しによく用いられています。
Tanaka,26,Tokyo Suzuki,32,Osaka Yamada,18,Yokohama
◆ CSVファイルを読み込む
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ファイルを変数に読み込む
◆ CSVファイルを変数に一括して読み込む
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";
}
■ 項目名付きCSVファイルを読み出す
◆ 項目名付きCSVファイルの読み込み
下記のように、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";
■ CSVファイルへの保存
◆ 項目名なし ReadCsvFile() データの保存
前節の 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() データの保存
同様に 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";
■ Excel形式のCSVファイルを読み出す
◆ Excel形式のCSVファイルとは
同じ CSVファイルでも、カンマ(,)、ダブルクォーテーション(")、改行などを含むデータの保存方法がアプリケーションによって異なります。例えば、Microsoft 社の Excel では、次のような法則に従っているようです。日本語を扱う場合は漢字コードをシフトJISにしてください。
◆ ExcelにおけるCSV保存ルール
通常はデータをカンマ(,)で区切って保存します。改行コードは \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
◆ Excel形式に対応するための改造
前出の 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の実装
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)をカウントすることにより、データ中のカンマ(,)やダブルクォーテーション(")に対応しています。
■ Excel形式のCSVファイルに保存する
◆ Excel形式のCSVファイルに保存する
前出の 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;
}