簡易データベース

■ 簡易データベースを用いる(dbmopen, dbmclose)

◆ 簡易データベース

dbmopen() は、キーと値によって構成される簡易的なデータベースファイル data を開き、これを連想配列に関連付けます。第3引数には作成するファイルのパーミッションを指定します。以降、連想配列への代入や参照は、簡易データベースファイルへの設定や参照になります。dbmclose() は簡易データベースを閉じます。

◆ データベースへの書き込み

dbmopen() により、連想配列 %xx とデータベースファイル data を関連付け、%xx に値を代入することにより、データベースに値を保存します。

dbm1.pl
dbmopen(%xx, "data", 0644);
$xx{'NAME'} = "Tanaka";
$xx{'AGE'} = "26";
dbmclose(%xx);

データベースファイル data は、実際には data.dir と data.pag という 2つのファイルで保存されます。データベースファイルには、「NAME の値が Tanaka」、「AGE の値が 26」という情報がバイナリデータとして保存されています。

◆ データベースからの読み込み

連想配列 %yy とデータベースファイルを関連付け、データベースの内容を読み出します。

dbm2.pl
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 は読み書き両用モードでオープンすることを意味します。

sdbm.pl
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ファイルを読み込むには次のようにします。

csv.pl
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ファイルを読み込んでこれを変数の値として保持する場合、次のようなサブルーチンを定義しておくと便利です。

readcsv.pl
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() を次のように改造します。

readcsv2.pl
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ファイルに保存するには次のようにします。

savecsv.pl
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ファイルに保存するには次のようにします。

savecsv2.pl
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() サブルーチンの実装は次のようになります。

excel.pl
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() の実装は下記のようになります。

excel.pl
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;
}

Copyright (C) 2002 杜甫々