Perl/CGIプログラムのパターンマッチ(1)

今回は、Perl/CGIプログラム学習の山のひとつ、パターンマッチについて学習していきます。

  1. 編集前記
  2. パターンマッチとは?
  3. パターンマッチの基礎
  4. 編集後記

編集前記

今回の編集前記は、恥ずかしながら Perl/CGIプログラミング を覚えたてのころやってしまったミスについて暴露したいと思います。

それは、 splice関数 を使って 配列 の任意の要素を削除するといった単純な作業だったんですが、配列そのものの性質を実感した出来事だったので今回掲載します。

あのときは状況によって削除する配列の要素番号が変化していたので、ここでその部分だけを書いてもわからないと思うので…

今回は、配列要素をひとつおきに削除するといったことを考えてみましょう。

以下の例を見てください。

#!/usr/bin/perl

use strict;

my @array = (0..9);
my @delete = ();

for (@array) {
next if $_%2;
push @delete, $_;
}

for (@delete) {
splice (@array, $_, 1);
}

print "Content-type: text/html\n\n";
for (@array) {
print;
}
exit;

このPerl/CGIプログラムの流れを説明すると…

まず、@arrayに0から9までの値が代入された要素10の配列を準備します。

このとき、値は0からはじまっているので、要素番号と代入された値は等しくなっています。

次に、削除する配列要素番号を、@deleteにピックアップします。

for (@array) {
next if $_%2;
push @delete, $_;
}

nextとは、この後に書かれている ループ処理 を1回飛ばして、次のループ処理に移行させてくれる関数です。

「next if $_%2;」となっているのでわかりにくいですが…

これは、「if ($_%2) {next;}」と同じ意味です。

if文 内の動作がnextだけなので、「{」と「}」をはずして前に持ってきているだけです。

ついでに、if文の条件式のカッコもはずしています。

if文内の条件式は、2で割ったときに余りがでれば成立するようになっています。

つまり、奇数の場合はnext関数により次のループ処理に飛ばされ…

それ以外の0を含めた偶数は、「push @delete, $_;」が実行されるというわけです。

これで0を含めた偶数のみがピックアップされました。

次はピックアップした要素を、splice関数を使って順に削除していきます。

for (@delete) {
splice (@array, $_, 1);
}

という部分ですね。

これでみごと0を含めた偶数番目の要素のみが削除されたはずでした。

しかしこのPerl/CGIプログラムを実行すると、「124578」と表示されます。

見てわかるように配列「@array」の要素はひとつおきに削除されていませんよね。

どうしてこうなったと思いますか?

当時はどうしてこうなったのかわかるまで少し時間がかかりました。

どうしてこうなったかというと…

それは、配列は常に要素番号0から順に並べられた変数群であるという体裁を保とうとするからです。

つまり、splice関数で配列の途中の要素を削除できても、それ以降の変数の要素番号が変化し順に詰められていくのです。

ちょうど列車の連結を思い浮かべていただければわかりやすいかと思います。

間の車両をとり除いてしまうと列車が2つに分かれてしまいますよね。

すると、後ろは動力を失うので困りますよね。

なので、後ろの車両群は前に詰められ連結されます。

これと同じことが配列の要素を削除するときにも起こっているのです。

shift関数やpop関数を使っているときには気にする必要ありません。

さらに、splice関数でも、配列の要素1つだけを削除する場合には上記プログラムで問題ありません。

問題は、splice関数を使って配列の要素を削除した瞬間、それ以降の要素番号が詰められ、結果配列全体の構成が一瞬で変化してしまうことにあります。

なので上記のような場合だと、削除リスト「@delete」と実際の「@array」との間で誤差が生じることになるのです。

性格には、ループ2回目からおかしくなり始めます。

ではどうすればよいか…

いろんなやり方がありますが、一番簡単な解決方法として、以下の例を見てください。

#!/usr/bin/perl

use strict;

my @array = (0..9);
my @delete = ();

for (@array) {
next if $_%2;
push @delete, $_;
}

for (reverse(@delete)) {
splice (@array, $_, 1);
}

print "Content-type: text/html\n\n";
for (@array) {
print;
}
exit;

このPerl/CGIプログラムを実行すると、「13579」と表示されます。

今度はちゃんとひとつおきになりましたよね。

splice関数を使って配列の任意の要素を削除する場合、前の要素から削除してしまうとそれ以降の要素番号が変わってしまうので、後ろから削除しています。

上記の例ではreverse関数を使って、削除リスト「@delete」の後ろから削除したい要素番号を取り出し削除させています。

ものすごく当たり前な話なので、わかる人にとってはさぞ退屈な話だったことでしょう。

でも、Perl/CGIプログラミング初心者にとっては、多少なりとも参考になったかと思います。

それでは今回の学習に移っていきましょう。

今回は、Perl/CGIプログラム学習の山のひとつ、パターンマッチについて学習していきます。

Perlはもともとテキスト処理に優れたプログラム言語です。

ですから、パターンマッチをある程度使いこなせれば、Perl/CGIプログラムに関して少し自信が出てきますよ。

しかし、Perl/CGIの世界では、「パターンマッチは覚えにくくわけのわからないもの」という伝説がまことしやかにささやかれているのも確かです。

その伝説というのは、半分ホントで半分ウソです(笑)。

なぜかというと、パターンマッチには正規表現が含まれている場合がほとんどだからです。

正規表現というのはものすごく乱暴に言ってしまうと、文字列の雰囲気を表した記号の羅列です。

ですから、ある程度経験をつんでいないと、何を表したものなのかわからない場合がほとんどです。

つまり、正規表現がわからないと、パターンマッチもわからないことになり、それが原因で、プログラムの動作を理解できないということにもなりかねないわけです。

そんなことを繰り返せば、苦手意識も生まれますよね。

なので、Perl/CGIに限らず、プログラム自体に苦手意識を持っている場合は、ここはちょっと、がんばらなくてはいけないポイントの一つになるかもしれません。

しかし今回も、できるだけわかりやすく、感覚的に理解できるようひとつひとつ説明してあります。

ですから、今回始めてパターンマッチと正規表現を学習する場合も、すでにパターンマッチと正規表現に対して苦手意識を持っている場合も、一緒に1から学習していきましょう。

ではそもそもなぜ、パターンマッチと正規表現が一緒に扱われることが多いのかというと…。

それは、正規表現というものの性質をある程度理解すれば見えてきます。

正規表現というのは、記号を並べて何らかの文字列をあらわした暗号です。

ですから、それ単体では文字列の雰囲気は表現できても、プログラムに何らかの指示を出すことはできません。

いやー、厳密に言うとできなくもないんですが…。

いきなりそこから入ると、半分以上の読者さんが脱落することになるので、まずは、パターンマッチを学びつつ、ついでに正規表現とも触れ合っておきましょうというのが今回の狙いです(笑)。

それに、パターンマッチと正規表現は、セットで使われることが多いですからね。

ふたつでひとつだと言っても、過言ではないぐらいです。

なので今回は、パターンマッチと組み合わせて、正規表現も学習していきましょう。

そういえば以前、パターンマッチと正規表現を駆使して、 数字や文字で文字列を分解するsplit関数 を作成しましたよね。

ぶっちゃけ今回は、あの続きのようなものです。

split関数は出てきませんが…。

それでは、はじめましょう。

パターンマッチとは?

Perl/CGIプログラム中で文字列を検索するとき、文字列全体を指定するのではなく、文字列の一部や、パターン化されたものであれば、それを手がかりに検索することがあります。

そのときに使用するのがパターンマッチです。

これは文字の意味そのままで、パターンにマッチするかどうかということですから、わかりやすいと思います。

身近なところでは、インターネット検索を思い浮かべていただければわかりやすいかと思います。

例えば「日本」で検索した場合…。

日本人

日本語

全日本プロレス

がんばれ日本

などの文字列がヒットするわけです。

「日本」という単語が含まれている文字列がピックアップされているだけですから単純です。

パターンマッチだけであればこれでいいのですが、これではあまりにも大雑把なので、Perl/CGIプログラムでは正規表現をプラスするわけです。

正規表現というのは以前にも書きましたが、あらかじめ定義された文字や記号を使って、文字列の雰囲気を表す手段のことです。

つまりただ「日本」という単語が含まれている文字列すべてではなく、「○○な感じで日本が使われている文字列」だけをピックアップさせることができるようになるわけです。

例えば、「日本の前後に何らかの文字が存在する文字列」と指定した場合は…。

前述の文字列群の中では「全日本プロレス」という文字列だけが該当するわけです。

さらに条件を変えて、今度は「文字列の最後に国名が使われているもの」とした場合は…。

「がんばれ日本」という文字列がピックアップされます。

さらに、国名である「日本」だけを抽出することだってできます。

これが正規表現を使ったパターンマッチです。

おもいっきり乱暴な例ですが、なんとなく雰囲気はつかんでいただけたのではないかと思います。

パターンマッチの基礎

それでは具体的に、パターンマッチを使った Perl/CGIプログラム を見ていきましょう。

パターンマッチの書式

パターンマッチの書式はこんな感じです。

「/パターン/」

区切り文字「/」でパターン文字または文字列をはさみます。

split関数の第一引数と同じ感覚で使えます。

パターンには文字や文字列を指定したり、正規表現はもちろん、 16進コード なども使えます。

パターンにアルファベットを使用する場合は、大文字と小文字は区別されるので注意しましょう。

split関数のときもそうでしたよね。

それと、区切り文字「/」については、これもsplit関数のときと同様に変更可能です。

変更方法も同じで、頭に「m」を記述し区切り文字を指定します。

split関数について忘れてしまった場合は、以前の記事を読み返して復習しておきましょう。

パターンマッチが成立すると、「1」が返されます。

不成立の場合は何も返されません。

このような性質を持っているので、通常はif関数と組み合わせて使います。

応用として、直接「and」や「or」演算子と組み合わせて使うこともあります。

このあたりも忘れてしまった場合は、以前の記事を読んで復習しておいてくださいね。

結構大事なポイントです。

文字指定

それでは基本中の基本、文字列内に特定の文字が含まれているかを調べるパターンマッチから行きましょう。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/e/) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「OK」と表示されます。

文字列「Perl/CGI」内に、「e」が含まれているかをパターンマッチを使って調べています。

もし、含まれていれば「OK」を、そうでなければ「NO」を出力するように指定しています。

ifとelseの関係はわかりますよね。

わからない場合は、以前の記事を読み返しておいてくださいね。

そして問題は、if文の条件式「/e/」ですよね。

パターンマッチなのはわかりますが、それだけしかないですよね。

つまり、比較する相手がいないんじゃないか?

そう感じたかもしれませんね。

パターンマッチの区切り文字「/」で、「e」をはさんでいることはわかりますよ。

でもそれだけしか書かれていないので、本当に文字列「Perl/CGI」と比較しているのかよくわからないですよね。

答えは見てわかるように、しっかり比較しています。

ためしに「/e/」ではなく、「/a/」にしてみてください。

今度は「NO」と表示されますよ。

これは、Perl/CGIプログラムお得意の「省略」という性質を利用したパターンマッチです。

if文の条件式にパターンマッチしか書かれていないときは、特殊変数「$_」と比較するようになっています。

このような書き方では比較している感じが無いので、違和感があると思います。

まぁでも何回か使えば慣れていくので心配ないですよ。

それに、「$_」と連動しているということは、ループ処理の書き方によっては、結構すっきりしたコードになります。

希望としては、わざと「$_」と連動させる勢いで、パターンマッチを使っていただければうれしいですね(笑)。

区切り文字指定

今度は、パターン内に区切り文字が含まれていた場合の例です。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (m|/|) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「OK」と表示されます。

パターンマッチの区切り文字はもともと「/」ですよね。

ですが、パターン内に区切り文字と同じ文字があると、パターン終了だと判断されてしまいます。

そんなときは今回のように、頭に「m」を記述し、区切り文字を変更します。

この例では区切り文字を、「|」にして、パターン文字である「/」をはさんでいます。

こうすれば、区切り文字とパターンが重複しないですみますよね。

他には、お決まりの「\」を使って区切り文字と区別する方法があります。

「m|/|」のように区切り文字を変更するのではなく、区切り文字はそのままに「/\//」としてもOKです。

これも、split関数のときにやりましたよね。

文字列指定

今度は文字列を指定したパターンマッチです。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/CGI/) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「OK」と表示されます。

パターン部分を文字列に変えただけで、あとは変わっていません。

簡単ですよね。

もちろんこれは、「CGI」という文字列を含んでいるときのみに成立します。

何らかの文字が抜けているときはもちろん、文字の順番がおかしくても成立しません。

例えば「CG」や、「CGAI」「ICG」などでも成立しないということです。

でも、パターンの文字列部分が「[」と「]」ではさまれていた場合は成立します。

つまり、「/[CGI]/」だったら成立するということです。

これもsplit関数のときにやりましたよね。

なぜ成立するのかわからない場合は、以前の記事を読み返してみてください。

と、まぁ面倒な説明をまるなげすることもできるんですが(笑)。

大切な部分なので復習をかねて説明しますと…。

「[」と「]」ではさまれた文字列というのは、ひとつの文字列ではなく、1文字ずつのパターンだと解釈されてしまうのです。

ようするに、「/[CGI]/」の場合であれば…。

「CGI」という文字列が含まれているかではなく、「C」か「G」か「I」のどれかが含まれているかというパターンに、解釈が変わってしまうというわけです。

したがって、「CG」や、「CGAI」「ICG」などでも成立してしまうということです。

このあたりは少しややこしいと感じるかもしれませんが、パターンマッチの意味自体が変わってしまうところなので、しっかり覚えておきましょう。

複数指定

今度は複数のパターンを指定する方法についての例です。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/PHP|Perl/) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「OK」と表示されます。

パターンが1文字ずつであれば、先ほどのように全体を「[」と「]」ではさめばすみます。

長い文字列であっても、勝手に分解して1文字ずつのパターンとして扱ってくれます。

しかし、複数の文字列をパターンとして指定したい場合には、それぞれの文字列の間に、「|」を入れて区切るようにします。

「|」は指定したいパターンの数に応じて、いくつでも使用することができます。

ここでは文字列の区切りとして使っていますが、当然、パターン文字としても使えます。

もし、「|」自体をパターン文字として使用したい場合には、直前に「\」を付けます。

つまり、「\|」にするということですね。

「|」を使うと、複数のパターンをスマートに表現できますが…。

もし、「|」を使わずに同じ結果を求めようとするには、どうすればよいと思いますか?

すでに学習済みになっていることを生かせば簡単ですね。

答えは…。

「/PHP|Perl/」ではなく、「/PHP/ or /Perl/」とかきかえればOKです。

または、「/PHP/ || /Perl/」でもOKです。

まぁでも「|」を使わないということなので、こっちはルール違反かもしれませんが…。

まぁ他にも、elsifを使って条件式を細分化するなど、やりかたはいろいろあります。

どれが正解というのは無いですが、どれでも使えるようにしておきましょう。

任意文字指定

今までのパターン文字というのは、確実に当てはまるものを使ってきました。

例えば「CGI」や「e」といった感じで、文字または文字列そのものズバリをパターンマッチに使ってきました。

でも今度はもう少し、パターンマッチの審査を緩めるPerl/CGIプログラムの例です。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/Per.../) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「OK」と表示されます。

今までのパターンマッチは、パターン文字そのものが含まれているかどうかをチェックするものでした。

「$_」内の文字列に含まれている文字を、正確に指定しないと成立しないものでした。

はじめからペアとなる鍵と鍵穴が決まっていて、他の組み合わせというのは許されませんでした。

それでもよいのですが、パターンマッチによっては、正確に文字を提示できないときもありますよね。

そんなときは、今回のようなパターンマッチを使います。

今回のパターンマッチ部分というのは、「/Per.../」ですよね。

この「.」というのは、任意の1文字を表しています。

ということは、「..」と書けば何かの2文字が、「...」と書けば何かの3文字が当てはまります。

つまり今までのように、鍵と鍵穴のペアが決まっているのではなく、文字という鍵であれば、どの鍵でも通過できる鍵穴が登場したという感じです。

そして今回のパターンマッチは「/Per.../」ですから…。

「Per」という文字列の後に、何らかの文字が3文字続いていれば成立します。

つまり、「Perl/CGI」はもちろん…。

「Period」や「Perfect」「Per123」だって成立するというわけです。

もし、「.」を任意の1文字としてではなく、パターン文字として使いたい場合には、「\.」とします。

先頭指定

次は、ただパターン文字が含まれているかどうかだけではなく、文字列のどこに含まれているかまでこだわったパターンマッチの例です。

まずは、文字列先頭にフォーカスしたパターンマッチから…。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/^CGI/) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「NO」と表示されます。

パターンマッチ部分は、「/^CGI/」という感じで、パターン文字列「CGI」の前に「^」がついています。

この「^」というのが、先頭に来る文字または文字列を指示しています。

つまり「/^CGI/」というのは、「CGI」から始まる文字列のときにのみ成立するパターンマッチだという意味です。

今までの例からもわかるように、特殊変数「$_」内の文字列は変わっていませんから、パターンマッチの「^」が無ければ、成立することはわかりますよね。

しかし今回は、「^」がついていて、文字列「CGI」から始まることが条件になっていたため、成立しなかったというわけです。

もし、「^」を使ったまま成立させたい場合は…。

「/^CGI/」ではなく、「/^Perl/」とすればOKです。

あと今回はたまたま文字列でしたが、そうではなく、文字を指定することもできますからね。

それともし、「^」を先頭指定としてではなく、パターン文字として使いたい場合は、お決まりの「\」を直前に付加して、「\^」とします。

末尾指定

今度は、末尾に来る文字または文字列を扱ったパターンマッチの例です。

#!/usr/bin/perl

print "Content-type: text/html\n\n";

$_ = "Perl/CGI";
if (/Perl$/) {
print "OK";
} else {
print "NO";
}
exit;

このPerl/CGIプログラムを実行すると「NO」と表示されます。

今度は末尾の文字列を指定したパターンマッチです。

先頭指定が「^」だったのに対し、末尾指定には「$」を使います。

使い方としては、前述の「^」と同じ感覚で使えます。

ただ、「$」の場合は、先頭ではなく末尾指定だというだけです。

それと、「^」と「$」は同時に使用することができます。

その場合、先頭指定と末尾指定があるわけですから、自然と文字列全体を現すことになります。

まだまだパターンマッチについて解説していきたいのですが、残りの紙面があやしくなってきたので今回は以上です。

今回は基本的なことばかりで、さぞかしかったるかったかと思います。

まぁでも、基本が大切ですから、しっかり復習しておいてくださいね。

編集後記

今回の編集後記は、編集前記でnext関数が出てきたので、last関数を紹介します。

last関数もnext関数と同じようにループ処理内で使われます。

next関数は次のループ処理へ移行させるのに対し、last関数はループ処理を抜け出すことができます。

例えば以下のPerl/CGIプログラムを見てください。

#!/usr/bin/perl

use strict;

my $start = 3;
my $last = 7;

print "Content-type: text/html\n\n";
for (0..9) {
next if $_ < $start;
last if $_ > $last;
print;
}
exit;

このPerl/CGIプログラムを実行すると「34567」と表示されます。

変数$startの値から、変数$lastの値までprint関数で表示されます。

$startの値よりも小さければ、next関数でひたすら次のループ処理に飛ばされ…

$lastの値より大きくなれば、last関数によりループ処理を抜け出します。

それ以外の値はprint関数で出力されるという流れです。

これでlast関数についてある程度わかっていただけたかと思います。

next関数とlast関数はセットで覚えておきましょう。

今回の学習は以上です。

ありがとうございました。