【Webプログラミング - Code.015】CGI(Perl)ファイル処理 その8:「DBMを使おう! 中編」

   Code.015                                                 2003年01月27日発行
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■
                           【 Webプログラミング 】

                       〜 猫的プログラマーとその軌跡 〜
■━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━■

              ▼毎週月曜日に配信しています。
              ▼等幅フォントでご覧いただくとキレイに見えます。
              ▼登録・解除はこちらから可能です。
                < http://www.ichikoro.com/webp/ >
                  ※ぜひお友達にもご紹介ください(^^)/

こんにちは、編集長の勝部です。

先日の日経新聞の春秋(第一面の下)に編集後記で以前ご紹介した
「ブラックジャックによろしく」が取り上げられていました。
いやー、そんなに売れていたとはビックリです(笑)

でもこれは大人が読むマンガなんですよね。これほど考えさせられ
る読み物って、なかなかお目にかかりません。まだの方はぜひ一度
手にとってみてください。

    ※お詫び
      前回のメルマガ(Code.14)で激しい誤植がありました。
      Subject欄と本文あわせ「MDB」となっている箇所は「DBM」
      の誤りです。ご迷惑をおかけしまして申し訳ございません。

      また、最新の誤植情報につきましてはBBSの方に随時アップ
      しています。こちらもあわせてご覧いただければと思います。
        http://www.ichikoro.com/webp/bbs/sylpheed.cgi?c=r&n=17


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 CGI(Perl)ファイル処理 その8:「DBMを使おう! 中編」
──────────────────────────────────────
今回はDBMを『使う』方のCGIをご紹介します。
    ※詳しい実行方法と解説はソースの下の方(メルマガの
      真ん中あたり)にあります。


    Mission : 郵便番号から住所を調べるCGIを作れ
    ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
        ○問題
            7桁の郵便番号を入力すると、その住所を表示する
            CGIを作成せよ。今回はDBMを使用すること。


今回のサンプルを試すには、前回作成した郵便番号の
データが必要です。

    ・前回のメルマガ
      http://www.ichikoro.com/webp/bk/00059/
        ※ダウンロード後、データ変換CGIを
          実行してください。

データを用意したら、今度はDBMファイルの作成を行
います。下記のCGIを動作させてください。


───────────
   ソース1(DBM作成)
───────────

#!/usr/bin/perl

;#
;# DBMを作成する
;#

use SDBM_File;
use CGI::Carp qw(fatalsToBrowser);       #エラー時にブラウザへ表示
use Fcntl;

#-------------------------------------------------#
#                ▼ココを設定する▼               #
#-------------------------------------------------#
#■変換後のファイル
$file = "KEN_ALL_CONV.CSV";

#■作成するDBMの名前
$dbm_name = "SDBM_postcd";
#-------------------------------------------------#
#                   ▲ココまで▲                  #
#-------------------------------------------------#

#--------------------------#
#         作成開始         #
#--------------------------#
tie(%h, 'SDBM_File', $dbm_name, O_RDWR|O_CREAT, 0600);

#-- データを突っ込む --#
open(DAT, $file) or die("Can not open file:$file ($!)");
while(<DAT>){
    chomp;
    my ( $code, @addr ) = split(/,/);

    $h{$code} = join("", @addr);
}
close(DAT);

untie %h ;

#--------------------------#
#   処理終了のメッセージ   #
#--------------------------#
print "Content-type: text/html\n\n";
print "<H2>OK</H2>\n";
print "DBMを作成しました\n";

exit(0);

__END__



以下の二つのファイルが出来たことを確認してください。

    ・SDBM_postcd.pag
    ・SDBM_postcd.dir
        ※$dbm_nameの名前を変更した場合、
          *.pag, *.dir の二つのファイルができます。


これで準備は完了です。
次はいよいよ検索を行ってみましょう!



────────────
  ソース2(郵便番号検索)
────────────
#!/usr/bin/perl

;#
;#郵便番号検索CGI
;#

#----------------------------------------------------------------------#
#                            モジュール                                #
#----------------------------------------------------------------------#
use strict;                              #コーディングを厳格化
use CGI qw(:cgi);                        #フォームデータ取得
use CGI::Carp qw(fatalsToBrowser);       #エラー時にブラウザへ表示
use Fcntl;
use SDBM_File;

#-------------------------------------------------#
#                   ▼ココを設定▼                #
#-------------------------------------------------#
#■DBMファイル
use constant DBM_NAME => "SDBM_postcd";

#-------------------------------------------------#
#                   ▲ココまで▲                  #
#-------------------------------------------------#


#======================================================================#
#                           メインルーチン                             #
#======================================================================#
package main;
{
    my $search_flag = 0;           #検索処理を行ったか?
    my $hit_flag    = 0;           #検索した結果見つかったか?
    my $addr        = '';          #表示用の住所を格納する
    my $q           = new CGI();

    #-----------------------------------#
    #              引数取得             #
    #-----------------------------------#
    my $post_cd = $q->param("post_cd");    #郵便番号

    #-----------------------------------#
    #             ヘッダ表示            #
    #-----------------------------------#
    $| = 1;
    print $q->header( -type    => "text/html",
                      -charset => "Shift_JIS" );
    print_header();


    #-----------------------------------#
    #              検  索               #
    #-----------------------------------#
    if( $post_cd =~ /^([0-9]{7})$/ ){     #入力が正しければ検索
        my %h;
        tie(%h, 'SDBM_File', DBM_NAME, O_RDONLY, 0600);

        #----------------------------#
        #         検索開始           #
        #----------------------------#
        $search_flag = 1;               #検索フラグを立てる
        if( exists($h{$post_cd}) ){     #入力された郵便番号が存在するか
            $hit_flag = 1;              #あればヒットフラグを立てる

            #-- 表示用の住所を作成 --#
            $addr = $h{$post_cd};
        }
        untie(%h);
    }


    #-----------------------------------#
    #             結果表示              #
    #-----------------------------------#
    if( $hit_flag ){
        #-- ヒット --#
        print "<H3>〒$post_cd $addr</H3>\n";
    }
    elsif( $search_flag ){
        #-- ハズレ --#
        print "<H3>No Hit</H3>\n";
    }
    else{
        #-- 検索しなかった(入力ミス、初期起動時) --#
        ;
    }


    #-----------------------------------#
    #             フッタ表示            #
    #-----------------------------------#
    print_footer();

    #-----------------------------------#
    #              正常終了             #
    #-----------------------------------#
    exit(0);
}


#--------------------------------------------------------------#
#■ヘッダ表示
#    内容:ヘッダ部分のHTMLを表示する
#    引数:なし
#  戻り値:なし
#--------------------------------------------------------------#
sub print_header{
    print <<"END_OF_HTML";
<HTML>
<HEAD>
<META http-equiv="Content-type" content="text/html; charset=Shift_JIS">
<TITLE>郵便番号検索</TITLE>
<STYLE type="text/css"><!--
    FORM {
        margin: 0px;
    }
-->
</STYLE>
</HEAD>
<BODY bgcolor="#FFFFFF">
<FORM>
    郵便番号:<INPUT type="text" name="post_cd" size="10"><INPUT type="submit" value="検索"><BR>
    <SMALL>※半角数字7桁。ハイフン('-')を入力しない。</SMALL>
</FORM>
<BR>
END_OF_HTML

}


#--------------------------------------------------------------#
#■フッタ表示
#    内容:フッタ部分のHTMLを表示する
#    引数:なし
#  戻り値:なし
#--------------------------------------------------------------#
sub print_footer{
    print <<"END_OF_HTML";
<BR>
<HR>
<DIV align="right">All Right Reserved, CopyRight (C) 2003
<A href="http://www.ichikoro.com/webp/" target="_blank">Webプログラミング</A>
</DIV>

</BODY>
</HTML>
END_OF_HTML
}

__END__



───────
   実行方法
───────
(1)前回のデータを用意します。

    ・前回のメルマガ
      http://www.ichikoro.com/webp/bk/00059/
        ※ダウンロード後、データ変換CGIを
          実行してください。

(2)テキストエディタ(メモ帳やSimpleText)などでDBM作成CGIを
   保存してください。適当な名前(xxxx.cgi)でOKです。
   そのまま実行します。

(3)以下の二つのファイルが出来たことを確認します。

    ・SDBM_postcd.pag
    ・SDBM_postcd.dir

(4)検索プログラムの方を、テキストエディタなどで保存します。
   これもファイル名は適当でかまいません。同じようにデータ
   ファイルの指定だけします。

(5)郵便番号を検索してみます。
   全国版などを使用し、データ量を多くして検索すると
   テキスト版に比べ高速になっているのが一目瞭然だと
   思います。


CGIの詳しい実行方法については Code.001 をご参照ください。
http://www.ichikoro.com/webp/bk/00046/


───────
    解  説
───────
○DBMって何?
    もともとUNIXなどについていた簡易的なデータベースのことです。
    DBMは今回紹介するPerlは元よりC言語などからも使用することが
    できます。
    また一口にDBMといっても下記のようにいくつかの種類があります。

        ・ODBM
        ・SDBM
        ・GDBM
        ・NDBM
        ・Berkley DB

    それぞれに特徴があり、例えばSDBMは1レコードあたり1k byteしか扱う
    ことができません。それに比べNDBNは4k byteまで、Berkley DBにいたっ
    ては制限がありません。他にも検索速度やDBMのファイルサイズなどが
    異なります。

    詳しくはコマンドライン(DOS窓やシェルなど)から下記を
    参照してください。

        perldoc -m AnyDBM_File

    Googleで検索すると日本語訳のページも出てきます。

        http://www.google.co.jp/search?hl=ja&q=Perl+AnyDBMFile


○どのDBMを使かうか
    まずはサーバ上にどのDBMがインストールされているか調べてみましょう。
    管理者に質問するか、下記のプログラムを実行することで使用できるDBMの
    種類がほぼわかります。
        ※AnyDBM_Fileモジュールが同じ事を
          内部で行っています。

        #----------------------------------#
        #       調べたいDBを配列に         #
        #----------------------------------#
        @DB = (    "NDBM_File"           # NDBM
                    , "DB_File"          # Berkeley DB
                    , "GDBM_File"        # GNU DBM
                    , "SDBM_File"        # SDBM     **Perl標準(大抵入っている)
                    , "ODBM_File");      # ODBM

        #----------------------------------#
        #              調べる              #
        #----------------------------------#
        foreach $mod ( @DB ){
            #-- 無事にrequire出来れば“使える” --#
            if( eval "require $mod" ){
                print $mod,"\n";
            }
        }

    今回ご紹介したサンプルでは、Perlのバージョンが5以降であれば
    標準で組み込まれているSDBMを使用することにしました。


○どうやって使うの?
    基本的な流れは、テキストファイルとほぼ同じです。

        (0)前準備(use)
        (1)DBMを開く
        (2)操作
        (3)DBMを閉じる

    簡単なサンプルプログラムを用意しました。
    まずはこれを順番に見ていきましょう。

        ;#
        ;#DBMテスト
        ;#

        use SDBM_File;        #SDBMを使用する場合はコレを呼び出す
        use Fcntl;            #開く際のモード("O_RDONLY"など)を指定する際に使用

        #-- DBMを開く --#
        tie(%hash, 'SDBM_File', 'DBM_Name', O_RDWR | O_CREAT, 0600);

        #-- DBMに一行追加 --#
        $hash{'apple'} = 'あっぷる';

        #-- データ変更 --#
        $hash{'apple'} = 'リンゴ';

        #-- DBMにキーが存在するかチェック --#
        if( exists($hash{'apple'}) ){
            print "Apple is $hash{'apple'} \n";
        }
        else{
            print "Apple is NG\n";
        }

        #-- DBMからデータ削除 --#
        delete($hash{'apple'});

        #-- DBMを閉じる --#
        untie(%hash);


    まずは前準備といきましょう。

>        use SDBM_File;        #SDBMを使用する場合はコレを呼び出す
>        use Fcntl;            #開く際のモード("O_RDONLY"など)を指定する際に使用

    どのDBMを使用するかで、最初の一行は変わってきます。
    ここではSDBMを使用するため下記のようにしました。

>        use SDBM_File;        #SDBMを使用する場合はコレを呼び出す

    これがBerkeley DBであれば、

>        use DB_File;

    とします。
    Fcntlについては後述します。
        ※後のDBMは、use [DBM名]_File; とすることで
          使用できます。


○DBMを開く
    「開く」という言い方は tieを使用する場合あまり正しくないの
    ですが、イメージ的にはそんな感じです。

>        #-- DBMを開く --#
>        tie(%hash, 'SDBM_File', 'DBM_Name', O_RDONLY, 0600);

    それぞれが何を表しているかと言うと、

        tie( ハッシュ名                           # %hash
                , DBMの種類(useした際と同じ物)    # SDBM_File
                , DBMのファイル名                 # DBM_Name
                , DBMを開くモード                 # O_RDONLY(読み込み専用)
                , パーミション);                  # 0600


    ・ハッシュ名
        詳しくは後述しますが、DBMはハッシュを操作するだけで、
        データの挿入や削除、書き換えが行えます。その際に使用
        するハッシュを指定します。

    ・DBMの種類
        useする際のものと同じ文字列を指定すればOKです。
        SDBMであれば 'SDBM_File' とします。

    ・DBMのファイル名
        実際のファイル名を指定します。

    ・DBMを開くモード
        ここでは以下のいずれかを指定します。

            (1)読み込み専用     O_RDONLY
            (2)読み書き両用     O_RDWR
            (3)新規作成         O_CREAT
            (4)読み書き両用、存在しなければ新規作成     O_RDWR | O_CREAT

        Fcntlモジュールが使用できないと、これらの文字列は
        使用できません。ご注意を。

    ・パーミション
        DBMが存在せず、新規に作成する場合に設定される
        パーミション(実行権限)をここで指定します。
        0644などを指定しましょう。


○操作
    操作方法は非常に簡単です。

>        #-- DBMに一行追加 --#
>        $hash{'apple'} = 'あっぷる';
>
>        #-- データ変更 --#
>        $hash{'apple'} = 'リンゴ';
>
>        #-- DBMにキーが存在するかチェック --#
>        if( exists($hash{'apple'}) ){
>            print "Apple is $hash{'apple'} \n";
>        }
>        else{
>            print "Apple is NG\n";
>        }
>
>        #-- DBMからデータ削除 --#
>        delete($hash{'apple'});

    上記のようにハッシュ(連想配列)を操作するだけです。
    特筆すべきは、ハッシュを操作した時点でDBM上にも
    それが反映されている点です。
    つまり、

>        #-- DBMからデータ削除 --#
>        delete($hash{'apple'});

    という一行を実行した瞬間、ハッシュ上から無くなる
    とともに、DBM上からも消えてしまうのです。


○DBMを閉じる
    これはもうそのままですね。
    やはり untie を使用する場合、「閉じる」というのは
    適切な表現ではないのですが(^^;

>        #-- DBMを閉じる --#
>        untie(%hash);


○どれぐらい速くなっているの?(簡単ベンチマーク)
    簡単なベンチマークを取ってみたいと思います。
    ベンチマークのとり方について詳しく書くと、それだけで本が
    一冊かけてしまいますので、ここではかなり簡易的なやり方です。

    ローカル環境でWebサーバを動作させ、以下のソフト
    を使用して計測してみたいと思います。

        ・Webベンチ(Windows系)
          http://www.vector.co.jp/soft/win95/net/se179441.html

    いくつかのテストデータを抽出します。
    今回はランダムに取り出した以下の郵便番号を使用します。

        0620042  北海道,札幌市豊平区,福住二条
        3293437  栃木県,那須郡那須町,蓑沢
        5013754  岐阜県,美濃市,さくらが丘
        4211404  静岡県,静岡市,栃沢
        8996502  鹿児島県,姶良郡牧園町,三体堂(その他)

    テキストファイルの場合、南下するほどファイルの下の方にデータが
    あります。そのため検索時間が長くなることになります。それを踏ま
    えてみてみましょう。

    ・テキストファイル
        郵便番号   受信スピード(byte/second)
        -------------------------------------
        0620042    1,044
        3293437      589
        5013754      436
        4211404      404
        8996502      273

    ・SDBM
        郵便番号   受信スピード(byte/second)
        -------------------------------------
        0620042      919
        3293437      949
        5013754      996
        4211404    1,018
        8996502    1,030

    受信スピードは一秒間にどれだけの転送があったかを示すものです。
    つまり値が高ければ高いほど速いということになります。

    データ量が少ない場合は、DBMを使用してもしなくても変わりません。
    むしろ速い場合もあります。しかし大量のデータを処理しなければ
    ならない際には、結果は一目瞭然ですね。


○互換性の問題
    今回作成したSDBMのデータ(*.pag, *.dir)は、残念ながら他のDBMとは
    互換性がありません。変換プログラムなどを通す必要が出てきます。

    ここまでのことを考えた場合、少量のデータを扱う場合はテキスト
    ファイルをおすすめします。互換性に優れ、異なる環境間を移動す
    るのに適しています。またDBMと違い、テキストエディタなどで簡
    単に修正・加工できるのも強みです。

    何でもかんでもDBMを使えばいいという物ではありません。
    それぞれの長所・短所をしっかりと理解して使ってください。


───────
   次回予告
───────
SDBMには排他制御の機能がありません。
そこで次回はflockを使用できる Berkeley DBと、
SDBMなどでの排他制御方法をご紹介したいと思います。


───────
   配信予定
───────
ファイル処理編の配信一覧です。

    ・CGI(Perl)ファイル処理 その1:「ファイルを読む」
      http://www.ichikoro.com/webp/bk/00053/
    ・CGI(Perl)ファイル処理 その2:「ファイルへの書き込み」
      http://www.ichikoro.com/webp/bk/00054/
    ・CGI(Perl)ファイル処理 その3:「ファイルロック:flock 前編」
      http://www.ichikoro.com/webp/bk/00055/
    ・CGI(Perl)ファイル処理 その4:「ファイルロック:flock 後編」
      http://www.ichikoro.com/webp/bk/00056/
    ・CGI(Perl)ファイル処理 その5:「ファイルロック:mkdir編」
      http://www.ichikoro.com/webp/bk/00057/
    ・CGI(Perl)ファイル処理 その6:「ディレクトリ操作」
      http://www.ichikoro.com/webp/bk/00058/
    ・CGI(Perl)ファイル処理 その7:「DBMを使おう! 前編」
      http://www.ichikoro.com/webp/bk/00059/

    ・CGI(Perl)ファイル処理 その8:「DBMを使おう! 中編」<<今回
    ・CGI(Perl)ファイル処理 その9:「DBMを使おう! 後編(ロック)」


取り上げて欲しいテーマやご意見・ご要望はぜひ以下
までおよせください。

    ・BBS
      http://www.ichikoro.com/webp/bbs/

    ・メール
      mm-webp@ichikoro.com


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ここが知りたい! WebプログラミングQ&A
──────────────────────────────────────

※今回はオヤスミです。
  イキナリかよ!(三村風)

    ★募集中!
      Webプログラミングではこのコーナーで聞いてみたいご質問を
      受け付けております。メルマガで取り上げた内容と違っても
      OKです。お気軽にお寄せください。

              mm-webp@ichikoro.com
              (かつべへダイレクトに届きます)

───────
  分からない
───────
いまいちよく分からない場合は、以下で聞いてみることも
できます。

  ・サポートBBS
    このメルマガ専用のサポート掲示板です。
    勝部が(分かる範囲内で)ギモンにスパッとお答えします。
    メールで聞くより高速です。お気軽にお書き込みください(^^)/
      http://www.ichikoro.com/webp/bbs/

  ・CGIプログラミングML
    CGIなどWebに関する話題を繰り広げるメーリングリスト。
    このメルマガとは関係ありませんので発言時は注意を。
      http://www.ichikoro.com/cgi/ml/


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           編    集    後    記
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
配信が遅れてしました(^^;
というわけでQ&Aも今回はオヤスミです。
派手に誤植はするし。もう、ホントすみませぬ。


またまた週末は広島に行ってきました。
と思って色々書こうと思案していたのですが、考えているうちに
何だか切ない気持ちになってきたのでまたにします(ーー;)


今回の旅行のお供に、売日直後(去年の秋くらい)に買って以来、ほっぽっておいた
ままだった村上春樹さんの「海辺のカフカ」を持っていったのですが、これが今回
の旅行にピッタシ。本には出逢うタイミングっていうのがあるもんなんだなぁと改
めて感じました。

何だか久しぶりにとりとめない編集後記ですな(^^;
すみません、何か情緒不安定です。
といっても体は元気なのでご心配なく。

また来週お会いしましょう! (^-^)/~~~
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

                   【 Webプログラミング Code Sample 】

                    発  行 : ichikoro.com
                発行責任者 : 勝部 麻季人
                              < katsube@ichikoro.com >
                 Webサイト : < http://www.ichikoro.com/webp/ >
            お問い合わせ先 : < mm-webp@ichikoro.com >

                            Powerd by まぐまぐ
  All Right Reserved, CopyRight(C) 2001-2003 Webプログラミング Code Sample
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■