このエントリーをはてなブックマークに追加

Perl応用

Perl応用編

入門編ではPerlの文法を学びました。しかし、それだけではまだ実用プログラムを作るには情報が足りません。Perlで実用プログラムを作るために、避けて通る事ができあないのが、モジュールとデーターベースとの連動、そして、正規表現です。

応用編では、モジュールの使い方、データーベース、正規表現について学びましょう。

モジュール

モジュールが使われる以前のPerlでは、よく使われるサブルーチン(たとえば、cgi.plやjcode.plなど)をプログラムの先頭でrequireで組み込んで使っていました。そのため、各サーバーの各ユーザーディレクトリの各cgi-binディレクトリにcgi.plやjcode.plが存在していました。

そこで、そういったよく使われる関数群を最初からPerlに組み込んでしまおう、というものが「モジュール」です。

Perl標準でついてくる関数と、モジュールとの違いは、たとえばJcodeは日本でしか使われないでしょうし、各国の言語に変換する機能をすべて標準でもたせてしまうと、Perl本体が非常に肥大化してしまうでしょう。

モジュールは必要に応じてPerlに組み込むことのできる追加機能といったところです。

モジュールの組み込み

モジュールの組み込みは、ルート権限が必要ですので、プロバイダのサーバーを利用している方は、プロバイダにどのモジュールが組み込まれているかお問い合わせください。自分でサーバーを構築している方は、以下のようにしてモジュールを組み込んでください。

・Jcode.pmの組み込み
perl -MCPAN -e shell
cpan> install Jcode

ここで、手動で設定ファイルを設定するかどうか聞いてきます。ここでnoと答えると、cpan.orgから最新版を探しますが、このサーバーは世界中の人がアクセスしてくるため非常に重いです。

ですので、手動設定にして、ダウンロードサイトを日本国内のミラーサイトにしておくと、比較的早くダウンロードする事ができます。

・CGI.pmの組み込み
perl -MCPAN -e shell
cpan> install CGI

あとは、Jcode.pmの組み込みと同様に操作してください。

・CPANを使わずに組み込む
CPANを使わなくとも、モジュールを手動でコンパイル&インストールすることができます。Perlのモジュールは大抵は先頭の文字か、全てが大文字で表記されたtar.gzファイルで配布されていますので、それをw3m等でダウンロードするか、Windows端末でダウンロードしてFTPでサーバーにアップした後、解凍します。

例)
tar xvzf CGI.pm-2.93.tar.gz
cd CGI.pm-2.93

普通、ソースをコンパイルする時はここの後に./configureとするところでしょうが、Perlモジュールの場合は、configureのかわりにMakefileを生成するスクリプトがPerlで書かれている事が多く、名前は Makefile.PLという名前になっています。このファイルを使い、Makefileファイルを生成します。

例)
perl Makefile.PL

また、Perlモジュールは、make testで正しくコンパイルできたかどうか検査できるようになっていますので、make install前に必ずテストします。

例)
make
make test
make install

この方法を使えば、CPANを使わなくともPerlモジュール(モジュール名.pm)をインストールすることができます。業務でサーバーのセッティングをやっている方(私とか)は、「今日はCPANのサーバーがダウンしていたので、サーバーをセッティングできませんでした」なんて許されないでしょうから、手動でもインストールできるようにしておきましょう。

CGI.pmを使ってみよう

サーバーにモジュールが組み込まれたら、(あるいはプロバイダに組み込まれていることが確認されたら)、さっそく使ってみましょう。

モジュールを組み込むためには、最初にモジュールを宣言する必要があります。

例)
use CGI;

requireとuseは違うの?

requireは、アセンブラやC言語でいうところのinclude、COBOLでいうところのCOPYと同じで、単に別のテキストファイルになっているものを貼り付けるという意味で、そのままそこに同じものを書いたのと、実行の上では何ら変わりありません。

しかし、useを使って組み込まれたモジュール(.pm)は、package 宣言された「クラス」です。

クラスって??

C++ではお馴染みのクラスですが、ここでPerl専門の人のためにもう一度ご説明しましょう。

クラスとは、「独立性の高い関数、変数の定義」といった所でしょうか。ここで重要なのが「独立性が高い」(カプセル化といいます)という事です。例えば、従来のrequire方式では、偶然同じ名前のサブルーチンがあるとエラーになってしまいます。また、偶然同じ名前の変数(グローバル変数)があると、ごっちゃまぜになってしまい、全然関係ないところで変数が書き換わったりしてしまいます。

しかし「クラス」を使えば、もし同じ名前のサブルーチンがあったとしてもクラスが違えばそれはまったくの別物として扱われます。同様に、同じ名前の変数名が別のクラスにあったとしても、それは別物として扱われます。

こうする事によって、まったく別の人が、自由な名前で作った関数や変数を、同じプログラムに自由に組み込むことができるようになり、自由度が非常に高くなります。

実体を宣言

CGIモジュールを宣言し、オブジェクトを$cgiに代入する。

$cgi = new CGI;

newはコンストラクタと呼ばれ、このクラスの実体を生成する関数です。C++では定義された時に初期化するために、クラス名と同一関数(メゾット名)を使いましたが、Perlではnew()という関数名がコンストラクタとして使われます。

したがって、これは次と同じ意味になります。

$cgi = CGI::new();

CGI::new();は、「CGIというクラスのnew()という関数を呼び出す」という意味です。結果としてオブジェクトが返ります。

オブジェクトって??

JavaScriptやVBなどではよく登場する、このオブジェクトとは、つまり「クラスで定義された関数や変数群の実体」といったところです。

「クラス」そのものは定義でしかなく、実体はメモリに展開されません。コンストラクタが呼ばれ、実体がメモリに確保された地点で、その実体が「オブジェクト」となります。「構造体」と違うのは、「オブジェクト」は変数だけでなくサブルーチン(メゾットといいます)も含まれるという点です。

アクセスメゾットって??

アクセスメゾットとは、「クラス内の変数を取り出す関数」です。

基本的に、クラス内の変数には直接アクセスしない事になっています。Perlではプライベート変数というものがないため、直接アクセスを禁止する事はできないのですが、クラスの「データーのカプセル化」という考え方を統一するため、できる限り直接アクセスは避けるようにします。

そのため、クラス内の変数を書き換えたり、取り出したりする場合は、データーを受け渡すための、「受け付け」となる関数を作ることになっています。これをアクセスメゾットといいます。

例)フォームから入力されたyour_nameの値を取り出す
$your_name = $cgi->param("your_name");

この場合、フォームから入力されたデーターを、$your_nameに代入するわけです。つまり、$cgiクラス内の関数param()を、引数 your_nameで呼び、リターンコードを変数$your_nameに入れます。

ここで、param()がアクセスメゾットになります。

->って??

->って何でしょう??矢印信号のマークでしょうか?? あまりC++系のプログラミングに毒されてくると、街の矢印信号を見るたびに、この記号を思い出してしまうのですが、これは矢印信号ではなく、矢印演算子といいます。オブジェクトに対して->記号が使われた場合は、「このオブジェクトの中の1要素」を指します。

つまり、$cgi->param('your_name')は、$cgiというオブジェクトの中の、param()という関数(メゾット)、という意味です。

ところで、C++では、オブジェクト.メゾットという風に、『オブジェクトの…』という場合ピリオドで区切っていました。そういえば、JavaScriptもピリオドでしたね。これはC系の言語には、構造体の頃から、矢印演算子はポインターを指すもので、メモリーに展開された実体を指す場合はピリオドを使う、という考え方があるからです。そのため、Perl6からは矢印演算子ではなくピリオドが使われます。

とりあえず、Perl5を使う場合は、オブジェクトの一部を示すために矢印演算子を使うと思ってください。

C++との相違点

ではここで、C++とごっちゃにならないように、相違点をまとめてみましょう。
C++ Perl
クラスの宣言 class クラス名 { packages パッケージ名
コンストラクタ クラスと同じ名前の関数 new()
デストラクタ ~クラス名() destroy()
thisポインタ thisという名前のポインタ $任意変数名=shift;
メゾットへのアクセス オブジェクト.メゾット(); オブジェクト->メゾット();
コンストラクタ、デストラクタの違いは慣れてくば使い分けも可能になるのですが、うっかりしてしまうのが、thisポインターが代入しないと使えないという点と、ピリオド演算子と矢印演算子の違いでしょう。Perlでは、クラス内で「ここのポインタ」は、スタックポインターに積まれてきますので、shift演算子を使ってスタックから取り出してから使います。

矢印演算子でオブジェクトを指すのはPerl5やPHP4で使われます。しかし、Perl5を使った後でC++を使おうとすると、どうしても混同してしまいます。(矢印演算子は他のC系の言語ではポインターを示すものなので)

サンプルプログラム

CGIモジュールを使ったサンプルプログラムをみてゆきましょう。

かつては、フォームからの入力を $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; のようにデコードする必要がありましたが、CGIモジュールができたために便利になりました。

このサンプルを実行するためには、あらかじめサーバーにCGIモジュールが組み込まれていることが前提となります。

form.html
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=EUC-JP">
<title>名前の入力</title>
</head>
<BODY>
<form action="./decode.cgi">
お名前:<input type="text" name="your_name">
<input type="Submit" value="送信">
</form>
</BODY>
</html>
decode.cgi
#!/usr/bin/perl

use CGI;

$cgi=new CGI;
$your_name = $cgi->param("your_name");

print "Content-type: text/html\n\n";
print "<html>\n";
print "<head>\n";
print "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=EUC-JP\">\n";
print "<title>結果</title>\n";
print "</head>\n";
print "<BODY>\n";
print $your_name;
print "さん、いらしゃいませ\n";
print "</BODY></html>\n";
form.htmlをブラウザから見える場所に置いておきます。また、decode.cgiを同じディレクトリに置き、実行属性をつけておいてください。このサンプルは、ただ、入力した名前に対して「○○さんいらっしゃいませ」と出るだけです。しかし、フォームから入力された値を変数に代入する方法はわかったと思います。

このサンプルを参考に、アンケートフォームなどを作ってみてください。

データーベース連動

Perlでデーターベースを扱うためには、あらかじめお使いのサーバーのPerlに、DBIモジュールとDBDモジュールを組み込んでおく必要があります。

DBIとは

Perlからデーターベースを読み書きするためのモジュールです。connect、prepare、execute等のメゾットが提供されます。データーベースの種類に関係なく同じメゾットを使う事ができます。

データーベースの種類による違いは、DBDモジュールで吸収されます。

DBDとは

DBIから呼び出され、データーベースの種類ごとに異なるモジュールが使われます。Perlでプログラムを作る側としては、DBDの違いを意識することなくデーターベースへのアクセスができるようになります。DBDには、

DBD::Oracle
DBD::Pg
DBD::mysql

などがあります。サーバーにインストールされているデーターベースに対応するモジュールを組み込みます。

モジュールの組み込み

perl -MCPAN -e shell

とし、CPANのシェルを起動します。

CPAN>install DBI
CPAN>install DBD::Pg

として、DBIおよびDBD::Pgをインストールします。
※注意
CPANのinstallを使うとCPANに登録されたモジュールの最新バージョンを探してインストールしようとしますが、サーバーのPerlのバージョンが古いと、モジュールのバージョンと合わずにインストールできない場合があります。

この場合、CPANを使わずにサーバーのPerlのバージョンに合ったモジュールを手動でインストールする必要があります。
コラム ::の読み方
C++などで「クラス」を使った事のある方はお馴染みと思いますが、::は「~というカテゴリーの中の~」という意味です。たとえば、DBD::PgはDBDカテゴリーの中のPgという意味です。

もし、これがPgだけだと、偶然頭文字にするとPgになるモジュールが2種類あった場合に同じ名前のライブラリが2種類できてしまいます。なので、Perlのモジュールは、○○カテゴリーの○○という風に指定するものがあります。

::は日本語でいうところの「の」と同様の意味なので、「の」と読むとわかりやすいと思います。

例)
DBD::Pg ・・・・ DBDのポストグレイス
DBD::mysql ・・・・ DBDのマイエスキューエル
Business::CreditCard ・・・・ ビジネスのクレジットカード

ローカルルールを決める

PHPと違い、Perlでデーターベースを扱うためには、さまざまなハンドルが必要となります。しかしながらハンドルだらけのソースは可読性が悪くなり、デバッグ効率も悪くなります。そこで、プロジェクト毎に変数名を固定するとソースの可読性が良くなります。できれば、同じ会社内には同じルールを使うのが良いでしょう。

ここでは、以下のようなローカルルールを定めます。経験則ですが、それぞれの変数が何の略かを覚えておくとミスが少なくなります。
変数名 意味
$dbiname データーベースの種類(Pg、mysql、Oracle等)
$dbname データーベース名
$hostname ホスト名
$username ユーザー名
$passwd パスワード
$dbhandle データーベース接続ハンドル
$query ステートメントそのもの
$sthandle ステートメント準備ハンドル
$result 取得したデーター
$rows 取得した行数

DBIを使う宣言

DBIモジュールを使うために、プログラムの先頭で宣言が必要です。
use DBI;
のように宣言します。DBDはDBIの中で宣言されるため、ユーザーレベルで宣言する必要はありません。

データーベースへの接続

データーベースに接続し、接続ハンドルを返します。

MySQLとPostgreSQLで与えるパラメーターに違いはありますが、基本的にデーターベースの種類によって異なるのはコネクト部だけです。もちろんステートメントにデーターベースの違いによる方言は多少ありますが、汎用的なクエリーを実行する限りは違いがないと言ってもいいでしょう。

書式
$dbhandle= DBI->connect("DBI:種類:コネクト文字列","ユーザー名","パスワード");
$dbhandle データーベースへの接続に成功すると、その接続に関する情報を記録するためのクラス(オブジェクト)が生成され、ハンドルが返ります。以降、この$dbhandleはステートメントの準備や、接続をクローズする時に使います。

接続に失敗した場合は「偽」が返ります。
種類 DBDの種類を指定します。PostgreSQLならPg、MySQLならmysql、オラクルならOracleになります。
コネクト文字列 コネクトするための諸設定を記述します。複数設定する場合は;(セミコロン)で区切ります。

・MySQLの場合
database=データーベース名;host=ホスト名

・PostgreSQLの場合
dbname=データーベース名;host=ホスト名;port=ポート番号

※PostgreSQLでは、ローカルホストに接続する場合、host=localhostは省略できます。同様に、PostgreSQLのポート番号が5432だった場合、port=ポート番号も省略できます。
ユーザー名 データーベースに接続するユーザー名を入れます。
パスワード データーベースに接続するユーザーのパスワードを入れます。
・エラー処理
もし、データーベースへの接続がエラーになった場合、そこでプログラムを終了させないと、それ以降のデーターベースへのアクセスは全てエラーになってしまいます。そこで、エラーが発生した時に備えて、以下のように記述します。

$dbhandle= DBI->connect("DBI:種類:コネクト文字列","ユーザー名","パスワード") || die $DBI::errstr;

これは、エラーが発生した際は、DBIクラスのメンバー変数errstrを表示させてプログラムを終了するという意味です。こうする事で、データーベースへの接続に失敗した場合、失敗した原因が表示され、デバッグが楽になります。
コラム || die とは

||はPerl言語でorを示します。

||は左辺から評価していきます。たとえば、if ($a==$b || $c==$d) という場合、最初に$a==$bを評価し、真ならばそれ以降は右辺は評価しません。これはorはどこかが真になれば全体が真になるためです。

なので、Perlではしばしば||をエラー処理に利用します。ファイル操作などの命令の後に||dieと書く事で、左辺の命令が真ならば||die以降が実行されないため、右辺をエラー処理として利用することができます。

[die エラーメッセージ]

は、指定のエラーメッセージと行番号、スクリプト名を表示してプログラムを停止させる関数です。エラーメッセージの末尾に改行を入れないと、エラーの発生した行番号やスクリプト名も出力されるため、デバッグが楽になります。

ステートメントの準備

PHPでは、pg_query()やpg_exec()という関数でステートメントを実行しました。

しかし、Perlでは、ステートメントごとにそのステートメントを処理するためのオブジェクトを生成し、値を取り出し、オブジェクトを開放するといった手順が必要になります。(もちろん、どの言語でも内部的には同様の事が行われています。)

ステートメントとして、たとえば、
$query="select number,onamae,subject from keijiban order by number desc";

このようなSELECT文を実行する事にします。これは、掲示板テーブルから、シリアルナンバー・名前・タイトルをシリアル番号の大きい順から読むという意味です。掲示板などでツリーの一覧を作る場合によく用いるステートメントです。

$sthandle = $dbhandle->prepare($query) || die $dbhandle->errstr;
このステートメントを処理するためのオブジェクトを作成します。$dbhandle->prepareは、接続に成功した時に生成されたクラス($dbhandle)内の関数(メゾット)で、ステートメントを実行するための準備をします。成功すると、そのステートメントを処理するためのクラスが作成され、ハンドルを返します。失敗した場合は「偽」を返します。

接続に失敗した場合、die関数によりエラーメッセージ($dbhandle->errstr)を表示させ、プログラムを終了させます。

ステートメントの実行

ステートメントの準備に成功したら、ステートメントを実行させます。

$rows = $sthandle->execute() || die $sthandle->errstr;
このように、ステートメントを準備した時に与えられたハンドルを使い、execute()メゾットを呼び出します。

戻り値はSELECT文であれば行数を返します。insert、update、deleteの場合は影響を受けた行数を返します。失敗した場合は「偽」が返ります。ではもし、ステートメントの実行は「成功」で、結果が0行だった場合はどうなるでしょう。Perlでは0も偽であるため、結果が0行なら偽になってしまうんでしょうか?

そこで、「ステートメントの失敗」と、「成功だけど結果が0」は以下のように区別します。
execute()の結果 リターンコード 条件判断
成功で100行取得 100
成功で0行取得 0E0
失敗 undef
成功で0行取得した場合は、リターンコードは文字列の"0E0"となり、if文での条件判断では「真」となります。また、失敗した場合は「undef(未定義)」となりif文での条件判断は「偽」となります。

こうする事で、|| die や、if (!$rows) { エラー処理 } のようなエラー処理が可能となります。

また、Perlでは0E0は16進数ではなく”0E0”という文字列とみなされ、数値として評価すると「ゼロ」となります。したがって、以降の命令を、if ($rows>0) { }のように、「行が0以上なら」と記述することができ、可読性が良くなります。

ただし、オラクルなど、DBDによってはselect文のリターンコードとして、取得した行数を返さない場合があります。その場合は、->rowsというメゾットを呼んで行数を取得します。PostgreSQLやMySQLでもこの方法で取得できるため、汎用関数ではこちらの方法を使うと良いでしょう。

例)
$rows = $sthhandle->rows;

行の取得

executeメゾットの結果が「真」であり、0以上(つまり、0E0ではない)場合、行を取得する事ができます。行の取得部はif ($rows>0){}で囲むと可読性がよくなります。行を取得するには幾つかの方法があります。

1.普通に配列で読む(fetchrow_array)
@result=$sthandle->fetchrow_array;

このメゾットを使うことで、結果が1行ずつ順番に取り出すことができます。この例では結果がresultという配列変数に入ります。データーがなくなると「偽」を返すため、whileループ命令を使って、
while ( ($number,$onamae,$subject)=$sthandle->fetchrow_array) { 処理… }
ように、それぞれテーブルの列と同じ名前の変数に代入すれば、より可読性がよくなります。

この方法はいたってシンプルで非常に良いのですが、「仕様変更で$subjectの他に$emailを読むようにした」とかいう場合、カラムが1行ずつズレてしまい、思わぬバグになります。また、読み込む項目が多い場合にはリストが横長になり、かえって可読性が悪くなります。

2.配列へのリファレンスに入れる(fetchrow_arrayref)
$result=$sthandle->fetchrow_arrayref;

この場合、結果は$resultという配列ではなく、$resultに結果へのポインター(リファレンス)が入ります。なので、値を取得する際には、$$result[0]とか、$result->[0]みたいにします。しかしながら、この方法はあまりメリットは感じられません。

3.ハッシュ変数へのリファレンスに入れる(fetchrow_hashref)
$result=$sthandle->fetchrow_hashref;

この場合、結果は$resultという配列に入るのではなく、$resultにハッシュ配列変数へのポインター(リファレンス)が入ります。ハッシュ変数なので、値を取り出すときは、$result->{"列名"}のように取り出します。

ハッシュに入れる事で、列名で値を取得する事ができるため、仕様変更で列が増えた時にも、列がズレてバグになる可能性を少なくすることができます。しかしながら、データーベースシステムによって大文字だったり小文字だったり、大文字と小文字が別物だったりするため汎用性が減少します。

PostgreSQLでは小文字、オラクルでは大文字になり、MySQLでは大文字小文字を区別して扱うので注意しましょう。

4.バインドしてから読む(bind_col、bind_columns)
あらかじめ、列番号と変数を関連づけておく事ができます。

1列目を$numberという変数に結びつける。
$sthandle->bind_col(1,\$number);
1列目を$number,2列目を$onamae、3列目を$subjectという変数に結びつける。
$sthandle->bind_columns(\$number,\$onamae,\$subject);
bind_colは1列づつ関連付けるのに対し、bind_columnsでは全てのカラムを同時に関連付けます。bind_colは先頭の列が0ではなく1から始まります。また、bind_columnsは、取得した行と同じ数だけの引数を必要とします。

いずれの場合も、引数に指定するのは変数そのものではなく、変数へのポインター(リファレンス)で、変数名の前に¥$をつけて指定します。なので、ここで引数として指定する変数はあらかじめ宣言・初期化しておく必要があります。直前に初期化しておくと可読性が高くなり、バグが少なくなります。

このようにバインドしてから、$sthandle->fetchrowメゾットを実行すると、そのたびに関連付けられた変数に列の内容が代入されます。なので、whileループを使い、
while ($sthandle->fetchrow) {処理…}
というようなプログラムにする事で可読性がよくなります。また、この方法はmod_perlで処理が速くなり、一度に大量のデーターを処理する場合に適していると言われています。

この方法によるメリットは、仕様変更があった際に、バインドする部分のみを変更すれば良い事になります。したがって、バインドする部分のみ別ファイルにしておいて、requireするように作っておけば、仕様変更のたびに全ソースを眺める必要もなくなり、バグも出にくくなります。

ただし、事前に変数を宣言し、初期化し、リファレンスを渡す必要があるため、合計値のような結果が1行、1列だけに決まっているような場合などでは、かえって処理が煩雑になってしまいます。

このように、値を取得する方法は数通り用意されています。どの方法にも1長1短あり一概にどれがベストとは言えませんので、目的ごとに使い分けるのが良いと思います。

開放

ステートメントの準備をすることで、そのステートメントを実行するためのクラスが生成されました。しかし、これをそのままにしておくとクラスがどんどん作られ、メモリが無駄になってしまいます。

そこで、ステートメントの処理が終了した時にクラスを消去しメモリを解放させます。

PHPのPostgreSQL関数などでは、pg_free_resultで結果IDを開放せずにpg_closeをしてもエラーにはなりませんが、DBD::Pgではデーターベースへの接続を解除する前に開放を行っていないとエラーが発生します。

$sthandle->finish();

doメゾット

これまで、ステートメントを実行するために、準備→実行→列の取り出し→開放という手順を使ってきました。しかし、insertやupdate、deleteといった値を取り出す必要のない命令にこの手順をふんでいてはいささか面倒です。

そこで、それらの手順をまとめてやってくれるdoというメゾットがあります。
$rows=$dbhandle->do($query);
ここで、$rowsには変更した行数が入ります。変更した行数がない場合は、0E0が入ります。エラーが発生した場合も考えて、実際には以下のように作ります。
$query="insert into keijiban (onamae,subject) values ('$onamae', '$subject')";
$rows=$dbhandle->do($query) || die $dbhandle->errstr;
これは、掲示板に新規発言を登録する例です。この例でいくと、リターンコードが「偽」になった場合、エラーの原因を表示してプログラムを終了させます。

このdoメゾットを使うことで、準備、実行、取り出し、開放が一度にできるためプログラムの可読性が上がります。ただし、結果が影響をうけた行数しか返らないため、SELECT文で実行しても行数しか取得できない事になります。

selectrowメゾット

データーが1行だけに決まってる場合があります。たとえば、掲示板で1個の発言だけを出したい場合など、where句で読み込む行のシリアル番号を指定する場合です。
$query="select number,onamae,subject from keijiban where number='2'";
@result=$dbhandle->selectrow_array($query);
if (@result) {
   printf("[%s] %s : %s \n",$result[0],$result[1],$result[2]);
} else {
   printf("該当データーなし\n");
}
のように、selectrow_arrayメゾットを使う事で、準備→実行→列の取り出し→開放という手順を省略して1行分のデーターを取り出す事ができます。

データーが1行でかつ、1列に決まっている場合、例えば合計値や件数だけが結果として返る場合は、
$query="select count(keijiban) as count from keijiban";
$result=$dbhandle->selectrow_array($query);
if ($result>0) {
    printf("%s件のデーターがあります\n",$result);
} else {
    printf("該当データーなし\n");
}
このように、結果を配列ではなくスカラー変数に代入します。この場合、1行目の1列目の値しか取得できませんが、必要な結果がもともと1行で1列にきまっているので、このようにするとソースが短くなります。

同様に以下のものもあります。
メゾット 取り出せる範囲 結果のタイプ 取り出し方
selectrow_array 1行目だけ 配列 $result[列番号]
selectrow_arrayref 1行目だけ 配列へのリファレンス $result->[列番号]
selectcol_arrayref 各行の1列目 配列へのリファレンス $result->[行番号]
selectall_arrayref 全て 2次元配列へのリファレンス $result->[行番号]->[列番号]
selectrow_hashref 1行目 ハッシュ配列へのリファレンス $result->{"列名"}
selectall_hashreff 全て 二次元ハッシュ配列へのリファレンス $result->[行番号]->{"列名"}
selectallでは全ての結果が二次元配列に入ります。なので、結果として何千行もあるSELECT文を実行すると、サーバーのメモリーが大量に消費されることになり、他のユーザーに迷惑がかかります。なので、selectall系はなるべく使わず、結果が複数行になる場合は、普通にfetchrow_arrayを使った方が良いと思います。

これらのメゾットでは、結果が0なのかエラーなのかの区別ができません。そのため、実際にはこのメゾットを実行後に$dbhandle->errがセットされてないかどうかチェックした後、結果を表示させます。
if ($dbhandle->err) {
    printf("読み込みに失敗しました[%s] \n",$dbhandle->errstr);
    exit();
}

エラー処理の大切さ

データーベースの読み出しがエラーになる場合、大抵はステートメントの文法が間違っている場合ですが、まれにサーバーが負荷状態だったり、データーベースシステムのデーモン自体が落ちてる場合があります。

この場合、大抵はコネクトでエラーになるのですが、サーバーというものはその性質上、いつ、何がダウンするかわかりません。つまり、コネクトに成功した直後に、executeやdoでエラーになる事だってあり得るわけです。そのため、executeやdoなどデーターベースに対して読み込みや書き込みを行う場合、常にエラーチェックを行わなければなりません。

もし、エラーチェックがまったくないプログラムでも、デバッグ中は正常に稼動するでしょう。しかし、サーバープログラムを作る上では100回に1回、1000回に1回、いや、何万回に1回でもエラーが発生する可能性がある場所にはエラーチェックをしないと大トラブルになりかねません。

もし、掲示板やチャットのスクリプトで、画面に発言が全て表示されなかったとしたら、おそらく利用者は「サーバーが重いんだろう」と思い、リロードボタンを押してくれるでしょう。ですので、この程度であればエラー処理はさほど重要ではありません。

しかし、もし銀行ATMのプログラムを作る場合、預金残高を表示させる直前でサーバーがダウンしたとして、エラーチェックをせずにそのまま「残高0」と表示してしまうと大変な事になってしまいます。同様に、発注データーを読み込むシステムなどで、「注文がない」のか、「エラーなので注文が読み込めなかった」のかは、明確に区別して処理する必要があります。でないと、もしエラー発生時に注文がないものと判断しその日の発送をしないでおくと、後で大損害が発生してしまうでしょう。

というように、業務系のプログラムを作るのであれば、エラー処理は厳密に行う必要があります。ステートメントの処理の全てには||dieをつけてエラーを発生させるか、独自ルーチンでエラーである事をユーザーに伝えるようにします。エラー発生時にサーバー管理者にメールが行くようにすればベターです。

insert句を連続して行う

insert句のように、データー部だけが違って後は同じ、というステートメントを連続して行う場合、その都度ステートメント処理のオブジェクトを作っては開放、を繰り返すのは時間の無駄になります。

そこで、このような場合、挿入するデーターの部分だけを?としておいて、executeする際に?に該当する部分を渡す方法があります。
$query="insert into keijiban (onamae,subject) values (?,?)";
この例では、insert句のデータ部が(?,?)となっていて、この部分だけが変化することを示しています。ここでは、?をシングルクォートしません。シングルクォートしてしまうと、単に?という文字をinsertするという意味になってしまうからです。

この?に該当する部分は、executeメゾットの引数という形で指定します。
$sthandle = $dbhandle->prepare($query) || die $dbhandle->errstr;

$members_string = "name1;sub1,name2;sub2,name3;sub3";
@members_array = split(',',$members_string);
foreach $value (@members_array) {
     ($onamae, $subject)=split(';', $value);
     $sthandle->execute($onamae,$subject) || $sthandle->errstr;
}

$sthandle->finish();
この例では、まずテーブルに挿入するデーターを@members_arrayという配列に[名前;タイトル]という書式で入れておいて、そこから1つずつ取り出して、executeメゾットの引数として渡しています。

これで、1回のprepare~finishで複数の連続したinsert文を実行する事ができます。この例では挿入するデーターは固定でしたが、実際には別のテーブルから読み込んで配列に入れておいたり、エクセルのCVSファイル等を読み込んだりしても良いでしょう。

正規表現

正規表現とは、テレビ局各局の考案した放送不適格用語を回避する言い回し表現……ではなく(何のこっちゃ??)、正規表現とは、文字列を、文字列の並びの規則(パターン)で表現したものです。例えば、0123456789を正規表現で表すと、0-9となります。同様に、abcdefg…zを正規表現で表すと、a-zとなります。

もし正規表現を使わずに、aまたはbまたはcまたはdまたはeまたはf、を判別したい場合、

if ($a eq "a" || $a eq "b" || $a eq "c" || $a eq "d" || $a eq "e" || $a eq "f")

みたいに、||で繋いで条件文を全部書かねばなりません。これが、aからfまでだからいいですが、aからzまでのどれかだとしたら、aからzまでを||で結んで条件文を書く必要がでてきます。(もっとも、その方がプログラムの可読性はよくなりますが・・・)

しかし、これを、正規表現で表すと、

if ($a=~/[a-f]/) {

という風に表せます。したがって、aからzまででも、[a-z]という風に表す事ができます。
例1) $aは[a-g]にマッチするので、machi!!と表示される。
$a="c";
if ($a=~/[a-g]/) {
    print "machi!!\n";
} else {
    print "no machi!!\n";
}
ここでは、$aという変数を使いましたが、Perlでは全般的に$_という変数名を使うことによって、変数名を省略することができます。
例2)$a=~を省略した場合、暗黙に$_という変数名が対象になる。
$_="c";
if (/[a-g]/) {
    print "machi!!\n";
} else {
    print "no machi!!\n";
}

正規表現で使われる特殊文字

正規表現で用いる記号を調べるのに苦労するのは、検索エンジンで検索しにくいという点です。というのも、Googleでは、.や¥や^を検索しても除外されてしまうのです。そこで、ここでは検索で来る人にわかりやすいように、検索する人が調べるであろう読みも記述します。

.(ピリオド、点、ドット)
改行コード以外の任意の1文字にマッチします。a.は、a1でもa2でもaaでもマッチします。aのあとに改行コード(\n)がきていたらマッチしません。

¥(円マーク、エンマーク、バックスラッシュ)
正規表現で使う文字そのものを使いたい時に使います。たとえば、\.はピリオドそのものにマッチします。a\.は、a.という文字にだけマッチします。

^(べき乗、カレット、山形マーク)
「先頭に」という意味です。したがって、^abcは、abcdefgという文字列はマッチします。zzzzabcdefgという文字列はマッチしません。

$(ドルマーク)
「最後に」という意味です。abc$は、zzzzzabcという文字列はマッチします。abczzzzという文字列はマッチしません。

[ ](カギカッコ)
この[ ]で囲まれた中にある文字のどれかが含まれる、という意味です。[abcde]は、aかbかcかdかeがマッチします。[a-z]は、aかbかcか……zがマッチします。[0-9a-z]は、0から9までの数字か、aからzまでのアルファベットがマッチします。

|(縦線、パイプライン、縦棒)
「または」という意味です。複数の条件のいずれかにマッチさせたい時に使います。abcdefg | 123456 は、abcdefgまたは123456という文字列がマッチします。

[^](カギカッコの先頭に べき嬢、山形、カレット)
「以外の」という意味です。[^a-z]は、abcdefg……z以外の文字、という意味です。

量指定子

直前の文字が何個つづいているか、という事を指定する記号です。

*(アスタリスク、米マーク)
直前の文字0回以上の繰り返しにマッチします。a*は、aaaaaaaa という風にaがいくつ続いてもマッチしますし、aという文字がなくてもマッチします。

+(プラス)
直前の文字の1回以上の繰り返しにマッチします。*との違いは、0回ではいけないという事です。つまり、a+ならaという文字が少なくとも1回は出現しないとマッチしません。

?(はてな、クエスチョンマーク)
0回、1回の繰り返しにマッチします。つまり、a?は、aという文字1回にマッチしますし、aという文字自体がなくてもマッチします。

{ }(中カッコ)
繰り返しの数を指定できます。a{2}は、aaにマッチします。a{2,]は2回以上の繰り返しにマッチします。つまり、aはマッチしませんが、aaやaaaはマッチします。a{2,3}は、2回以上3回以内の繰り返しにマッチします。つまり、aaとaaaはマッチしますが、それ以上aがつづいても4文字目以降のaにはマッチしません。{0,}は*、{1,}は+、{0,1}は?と同じ意味です。

よく使うパターン(略記法)

\w(エンマーク小文字のダブリュ)
[0-9a-zA-Z_]と同じで、任意の数字または英文字またはアンダーバーという意味です。

\W(エンマーク大文字のダブリュ)
大文字の\Wは、小文字の\w以外という意味です。

\d(エンマーク小文字のディー)
[0-9]と同じで、数字という意味です。

\D(エンマーク大文字のディー)
大文字の\Dは数字以外という意味です。

\s(エンマーク小文字のエス)
[ \t\r\n\f]と同じで、スペース・タブ・改行・改ページのうちのどれか、という意味です

\S(エンマーク大文字のエス)
大文字の\Sは、小文字の\s以外という意味です。

正規表現を使った文字の分解

たとえば、スペースで区切ってある文字列があったとします。区切りの文字の文字数はわからず、とにかく連続したスペースはすべて区切りとみなしたい場合があります。この場合、

$_="aaaa    bbb cc   dddddd ";
@datas=split(/\s+/);

という風に、split関数の中で\s+(エンマーク小文字のエスプラス)と記述する事で、1個以上の空白文字を区切りとして配列変数に格納する事ができます。

この場合、
$datas[0]には、 aaaa
$datas[1]には、 bbb
$datas[2]には、 cc
$datas[3]には、dddddd
が入ります。

正規表現を使った文字列の置換

フォーマットが可変長の文字列から、数字の部分だけを取り出したい場合を考えます。

たとえば、「cyborg009」という文字列だった場合は「009」の部分だけを取り出したい場合。ただし、cyborgの部分は固定ではなく、長さが決まっていないとします。

この場合、数字の部分だけをマッチさせたいわけです。[0-9]と書いてもいいのですが、前述の通り、数字は\dでも良かったですよね。また、単に数字ではなく、「数字が1個以上連続している部分を取り出す」わけですから、+記号を足して\d+となります。

/\d+/

とすることで、「変数$_の中から、数字が連続している部分をマッチさせて、結果を$&に入れる」という意味になります。

Perlでは、特に指定しないと暗黙で使われる変数があり、その代表的なものが$_です。「対象となる変数名が省略された場合には変数$_を使う」という事がしばしばあります。

また$&は、直前のパターンマッチでマッチした部分が自動的に入ります。この場合は\d+なので、数字が連続している部分が入ります。

次に、取り出した文字列を加工してみましょう。ここでは、とりあえず「1を足す」という風にします。

$&+1

とすることで、009+1で、10となります。

また、桁数もそろえることにします。length($&)で、マッチした部分の桁数がわかりますから、それとsprintf文を使って、取り出した時と同じ桁数で出力させます。

今度は、元の文字列に置き換えてみましょう。置き換えにはs///文を使います。
s/置き換え前の文字列/置き換え後の文字列/指定
s///は、”置き換え前の文字列” に正規表現を使う事ができます。置き換え後の文字列は固定です。(置き換え後の文字列にも正規表現を使う時は、tr///を使います。)

s/\d+/$num/;

は、変数$_の中から(対象が指定されてないので$_が使われます)、\d+つまり数字が連続している部分を探して、$numの値に置き換えます。

ここまでをサンプルプログラムにしました。
例)文字列中の数字の部分を探して繰り上げる
#!/usr/bin/perl

$_="cyborg009";
ketaup();
printf("%s\n",$_);

$_="kikaida-01";
ketaup();
printf("%s\n",$_);

$_="ultra7";
ketaup();
printf("%s\n",$_);
exit();

sub ketaup() {
	/\d+/;
	my($keta)=length($&);
	my($format)="%0".$keta."d";
	my($num)=sprintf($format,$&+1);
	s/\d+/$num/;
}
実行結果)
cyborg010
kikaida-02
ultra8
解説)
cyborg009という文字列をセットして、ketaup()関数を呼びます。ここでは、数字の部分を+1して元の文字列に置き換えます。引数もリターンコードもない関数ですが、すべてグローバル変数$_が暗黙に使われています。

結果として、cyborg010という文字列が返ります。

最後に

最近、サーバーの性能が向上したため、これまでC言語(やC++)でなければ、という処理がどんどんPerlで実現可能になってきました。また、これからもサーバーの性能はますます向上していくと思います。

動的ページの作成に関してはややPHPに押されぎみのPerlですが、これからは今までCが主流だった常駐システムやサーバープログラムも、どんどんPerlで作られるようになってくると思います。

Perlなんて・・と思っていたC系のプログラマの皆さん、ここでPerlを本格的に学習してはいかがでしょうか?
このページの先頭へ
  広告