Home / Menu

CGI-Perlの基礎講座

CGI-Perlの基礎講座(p06)

(本ページは、KENT WEBのFantasyボードプログラムをステップ単位に説明しています。)

◆ コンテキスト

・概要

普通のプログラム経験者が、パールで一番最初に悩むコンテキストです。
コンテキストとは日本語訳では文脈で、ロジックの文脈によって同じ命令でも意味合いが違ってきます。
こんな言語、他にありません!
コンテキストには2種類あります。リストコンテキストとスカラーコンテキストです。以下に説明いたします。


サンプル1
 @buf=localtime;
 $buf=localtime;
 print "\@buf=@buf\n";
 print "\$buf=$buf\n";

実行結果

 @buf=32 4 18 28 10 99 0 331 0
 $buf=Sun Nov 28 18:04:32 1999


「@buf=」というのは、リスト(配列)を要求している。(リストコンテキスト)
「$buf=」というのは、スカラー(文字列や数値)を要求している。(スカラーコンテキスト)

同じ関数を使っても、リスト(配列)を要求するような書き方か、
スカラー(文字列や数値)を要求するような書き方かで、返ってくる値が違う。

ということを、

「localtime はリストコンテキストでは〜を返し、スカラーコンテキストでは〜を返す」

と表現します。


サンプル2
 @array=(1,2,3,4,5);
 $a=@array;    # スカラーコンテキスト
 @b=@array;    # リストコンテキスト
 ($c)=@array;  # リストコンテキスト
 print "\$a=$a\n";
 print "\@b=@b\n";
 print "\$c=$c\n";

実行結果2

 $a=5
 @b=1 2 3 4 5
 $c=1


[補足]
($c) とするとリストコンテキストになるので、
@array の最初の要素が $c に代入されます。
 ($c,$d,$e) = @array;
だと $c==1,$d==2,$e==3 になるわけです。


サンプル3
 open(IN,"filename");
 $line = <IN> … 最初の一行を $line に代入(スカラーコンテキスト)
 @buf = <IN> … 残り全ての行を @buf に代入(リストコンテキスト)

尚、本セクションの内容は、ラウンジの B-Cus氏の発言スレッドを参考にさせて頂きました。(いつもすみません)

正規表現(基礎編1)

・概要

UNIXをやり始めて10年、ついに正規表現と対峙する時がきました。 はじめに苦手意識を持ってしまって、ここまで来てしまいましたが、perlをやる以上避けて通れません。 やさしいいのから徐々に難しいのを説明していきます。

正規表現の概要は、とほほさんが以下のように説明しています。 「1文字以上のA」を「A+」と表したり、「A、B、Cのいずれか」を「[ABC]」で表したりする書き方。正規表現を 用いることで、複雑な検索を行うことができるようになる。

例を用いて、やさしいいのから徐々やっていきましょう。

まず、サンプルプログラムのテストデータ、family.txt を用意します。

family.txtファイルの内容


macha koike
yachu koike
chaichan koike
hiro koike
mama koike
papa koike
koike 6
ni-bo- horie yasashii
nee-nee horie chottokowai
60 horie
ma-kun murai
sachi murai
kazumasa murai
hisa

1、苗字が koike の人を検索
(サンプル01 sample01.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/koike/){
          print;
       }
  }

復習:上記は本来以下のロジックの横着版です。しかし、上記の方が普通の書き方みたいです。

ポイント:$_はwhile文の()の中に単独で<STDIN>と書くと、<STDIN>を評価するたびに$_に<STDIN>からの読み込み値がセットされる。
          printで出力するものを省略すると、$_を出力する。また、ファイルハンドルを省略すると STDOUT になる。

#!/usr/local/bin/perl
while($_ = <STDIN>){
    if($_ =~ m/koike/){
       print STDOUT $_;
    }
}

さて、サンプル01を実行すると以下が標準出力されます。
実行はコマンドプロンプトで以下の様に入力します。

% test01.pl < family.txt

macha koike
yachu koike
chaichan koike
hiro koike
mama koike
papa koike
koike 6

まずいことに、名前以外の koike 6 もマッチてしまいました。
そこで、koikeという文字列がデータの末尾に一致する場合のみに限定します。

(サンプル02 sample02.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/koike$/){
          print;
       }
  }
「$」は文字列の末尾にマッチする記号です。

% test02.pl < family.txt

macha koike
yachu koike
chaichan koike
hiro koike
mama koike
papa koike

今度は上手く行きました。
2、k で始まる人を検索
(サンプル03 sample03.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/^k/){
          print;
       }
  }
「^」は文字列の先頭にマッチする記号です。

% test03.pl < family.txt

koike 6
kazumasa murai

また、よけいな koike 6 がマッチしてしまいました。
そこで、koike 6 には数字が入っていますので、数字があるとNGにすればいいわけです。
ですが、発想を変えて、数字以外の文字が連続するとしても同じです。

(サンプル04 sample04.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/^k[^0-9]+$/){
          print;
       }
  }

% test04.pl < family.txt

kazumasa murai

今度は上手く行きました。

説明いたします。
まず、[〜]は文字クラスといって、[0-9]の場合は 0から 9までの文字コード1文字にマッチします。
[^0-9]は文字クラスの中に ^が付くことによって文字クラスの否定を意味します。文字列の先頭にマッチの^とは別物です。
[^0-9]+の+は一回以上の繰り返しという意味です。つまり、0-9以外の一文字の繰り返しになります。
[^0-9]+$はもうわかりますね。 $は末尾にマッチの$です。
3、1語の名前を検索
(サンプル05 sample05.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/^\S+$/){
          print;
       }
  }

% test05.pl < family.txt

hisa

\Sも文字クラスで、空白文字以外を表します。
つまり、^\S+$ は先頭が空白文字以外で、これがかつ一回以上の繰り返しで、末尾までになります。
4、3語の名前を検索
(サンプル06 sample06.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
       if(/^\S+\s+\S+\s+\S+$/){
          print;
       }
  }

% test06.pl < family.txt

ni-bo- horie yasashii
nee-nee horie chottokowai

\sも文字クラスで、空白文字を表します。\Sの反対です。
つまり、^\S+\s+\S+\s+\S+$ は先頭が空白文字以外で、これがかつ一回以上の繰り返した後、
空白文字を一回以上の繰り返し、空白文字以外一回以上の繰り返し、空白文字を一回以上の繰り返し、
空白文字以外一回以上の繰り返しで、末尾までになります。
4、その他の正規表現
■文字クラス

    [ABC]           A,B,Cのいずれか1文字    
    [A-Z]           A〜Zまでのいずれか1文字
    [A-Za-z0-9]     A〜Z, a〜z, 0-9までのいずれか1文字    
    [^ABC]          A,B.C以外の文字
    [^A-Z]          A〜Z以外の文字  
    \w              英数文字。[a-zA-Z0-9]と同様    
    \W              \w以外の文字
    \d              数値文字。[0-9]と同等    
    \D              \d以外の文字
    \s              空白文字    
    \S              \s以外の文字
    \b              単語の区切り
    .               任意の一文字

    補足説明、 /koike/ だと koikesでもマッチしてしまいますが、/\bkoike\b/だと回避できます。

■繰り返し 

    A+              1個以上連続したA(A, AA, AAA, ...)
    A*              0個以上連続したA(  , A, AA, AAA, ...)
    A?              0または1つの任意文字(  , A, B, C, ...)
    A{5}            5回繰り返し。 AAAAAと同じ
    A{3,}           3回以上繰り返し。 AAA+と同じ
    A{3,5}          3回以上5回以下繰り返し。 AAAA?A?と同じ

■グループと選択
   
    文字列を繰り返すときは()を使ってグループ化します。
   
    koi(ke)+        koike, koikeke, koikekekeなどにマッチします。
     
    いくつかのパターンのどれかにマッチさせるときは | を使います。

    macha|yachu     machaかyachuにマッチします。
    koike(X|Y)      koikeXかkoikeYにマッチします。

■位置指定
 
    ^              先頭
    $              末尾


■エスケープ
 
    ^\^            ^という文字で始まる行にマッチ
    \\             \自体にマッチ

    \は、特殊記号(\,/,^,$,*,+,?,{,|,},[,]など)の前では特殊記号の本来の作用をエスケープし、
    特殊記号以外では書かないのと同じになります。

    また、\Qと\Eの間に挿入された文字列は全部の文字の前に \ が挿入されたものと同じです。
    /\o\)\+\>/   と  /\Qo)+<\E/ は同じ。

正規表現(基礎編2)

・概要

引きつづき正規表現です。
置換処理、文字列の削除、最大マッチと最小マッチ、カッコを使った記憶、マッチ特殊変数について
お勉強致しましょう。


まず、今回もサンプルプログラムのテストデータ、family2.html を用意します。

family2.htmlファイルの内容(HTML的には参考にしないてください。)

<HTML>
<!-- family2.html  -->
<TITLE>Koike family Home Page</TITLE>
<H3>koike family Home Page</H3>
Welcome to this page!<BR>
Chaichan is charming!<BR>
Hi Chaichan! < HI Macha!<BR>
&#169;1999-2000 ChaicanPAPA's world &#169;1999-2000 10倍ズバリ site<BR>
&#165;5000
</HTML>

上記をブラウザでみると以下の感じになります。

koike family Home Page

Welcome to this page!
Chaichan is charming!
Hi Chaichan! < HI Macha!
©1999-2000 ChaicanPAPA's world ©1999-2000 10倍ズバリ site
¥5000
1、置換処理
&#169;は著作権の意味ですが、数字ではわかりずらいので、&copyと書いても同じなので、置き換えてみましょう。

(サンプル07 sample07.pl)
  
  #!/usr/local/bin/perl
  while(<STDIN>){
        s/&#169;/&copy;/g;
        print;
  }

説明いたします。
まず、s/$#169;/&copy;/g;はfamily2.htmlの$#169をすべて&copy;に置き換えます。
gスイッチ がないと最初にマッチしたものの一つの置換になります。
では実行してみましょう。

% sample07.pl < family2.html

      :
&copy;1999-2000 ChaicanPAPA's world &copy;169;1999-2000 10倍ズバリ site<BR>
      :


上手くいきました。

つぎに、Hi と HI を Helloに置き換えてみましょう。

(サンプル08 sample08.pl)
  
  #!/usr/local/bin/perl
  while(<STDIN>){
        s/hi/Hello/ig;
        print;
  }

gスイッチと一緒にiスイッチを指定すると大文字小文字の関係なくマッチするようになります。
では実行してみましょう。

% sample08.pl < family2.html

      :
Hello Chaichan! < Hello Macha!<BR>
      :

上手くいきました。
2、文字列の削除

置換文字列を空にするとマッチした文字列は削除されます。

(サンプル09 sample09.pl)

family2.txtの以下の2行の&#xxx; を削除してみましょう。

&#169;1999-2000 ChaicanPAPA's world &#169;1999-2000 10倍ズバリ site<BR>
&#165;5000
 
  #!/usr/local/bin/perl
  while(<STDIN>){
        s/&#\d{1,3};//g;
        print;
  }

では、実行してみましょう。

% sample09.pl < family2.html

       :
Chaichan is charming!<BR>
Hi Chaichan! < HI Macha!<BR>
1999-2000 ChaicanPAPA's world 1999-2000 10倍ズバリ site<BR>
5000
       :

上手くいきました。ちなみに \d{1,3} は 数字で1桁〜3桁の繰り返しです。
3、最大マッチと最小マッチ

つぎに、family2.htmlからタグを削除してみましょう。

タグは < ではじまり、 > で終わる文字列です。
「.」は任意の一文字で「*」は0回以上の繰り返しです。
ですので、以下のようなプログラムになります。

(サンプル10 sample10.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
        s/<.*>//g;
        print;
  }

では、実行してみましょう。

% sample10.pl < family2.html

Welcome to this page!
Chaichan is charming!
Hi Chaichan! < HI Macha!
c1999-2000 ChaicanPAPA's world c1999-2000 10倍ズバリ site
\5000 


あれれ、<TITLE>Koike family Home Page</TITLE>と<H3>koike family Home Page</H3>が
まるまる行毎、削除されてしまいました。
これは、<TITLE>の < から</TITLE>の > まで解釈されたためです。

実は、perlでは、パターンマッチがもっとも長い文字列とマッチする最大マッチなのです。
ですから、行毎削除されてしまいました。
では、<TITLE>、</TITLE>、<H3>や</H3>を削除するにはどうしたらいいでしょう?
それは最小マッチの ? を利用します。

(サンプル11 sample11.pl)

  #!/usr/local/bin/perl
  while(<STDIN>){
        s/<.*?>//g;
        print;
  }

では、実行してみましょう。

% sample11.pl < family2.html

Koike family Home Page
koike family Home Page
Welcome to this page!
Chaichan is charming!
Hi Chaichan! < HI Macha!
c1999-2000 ChaicanPAPA's world c1999-2000 10倍ズバリ site
\5000 

今度は上手くいきました。
4、カッコを使った記憶

まず、今回もサンプルプログラムのテストデータ、family3.txt を用意します。

macha koike sayuri koike chaichan このデータは「名前スペース苗字」の順番になっています。 これを「苗字カンマ名前」に替えてみましょう。 (サンプル12 sample12.pl) #!/usr/local/bin/perl while(<STDIN>){ s/(\S+) (\S+)/$2,$1/; print; } % sample11.pl < family3.txt koike,macha koike,sayuri chaichan 上手くいきました。ポイントは s/(\S+) (\S+)/$2,$1/; だけです。 説明いたします。 \S はスペース以外の一文字以外です。これが、スペースをはさんで2つになっています。 つまり、パターン全体が「名前スペース苗字」にマッチします。 (〜)は〜の記憶を意味し、先出た順に、記憶したものが $1,$2,$3・・・に格納されます。 今回の場合は $1 に名前、$2 に苗字が格納されます。そこで、 $2,$1に置き換えます。 また、$1,$2,$3・・・は置換文字列だけでなく、パターンマッチのあとで好きなように使えます。 (サンプル13 sample13.pl) #!/usr/local/bin/perl $n = 0; while(<STDIN>){ /(\S+) (\S+)/; ++$n; print "*** $n ***\n"; print "苗字: $2\n"; print "名前: $1\n"; print "\n"; } % sample13.pl < family3.txt *** 1 *** 苗字:koike 名前:macha *** 2 *** 苗字:koike 名前:sayuri
5、マッチ特殊変数

パターンマッチしたときに「マッチする前までの文字列」「マッチした文字列」「マッチした後の文字列」
が各々、$` , $& , $' に格納されます。では、プログラムで確かめてみましょう。

(サンプル14 sample14.pl)

  #!/usr/local/bin/perl
  $n = 0;
  while(<STDIN>){
        chomp;
        / \S+ | /;
        ++$n;
        print "*** $n ***\n";
        print "AAA: $`\n";
        if($& ne " "){
           print "BBB:" . substr($&, 1, length($&) -2) . "\n";
        } 
        print "CCC: $'\n";
        print "\n";
  }
 
% sample14.pl < family3.txt

*** 1 ***
AAA:macha
CCC:koike

*** 2 ***
AAA:sayuri
BBB:koike
CCC:chaichan

説明いたします。

まず、chompは $_ の右側の改行コードを切り落とします。(フィルターの定石の一つ)
/ \S+ | /; は「左右に空白を持つ非空白1文字以上か、または、1文字の空白」のマッチです。

つまり、今回の場合は、
*** 1 ***は「1文字の空白」のマッチで、
*** 2 ***は「左右に空白を持つ非空白1文字以上か」にマッチします。

そして、$` と $& と $' に各々の値が格納されます。
尚、$&には前後にスペースが入っていますので、substr($&, 1, length($&) -2)で削除します。

正規表現はこのぐらいにします。
わかってくると結構楽しいですね。もっと早くマスターすればよかった...。
ちなみに、基礎編1、2だけで、応用編はありません。
みなさん、各自、正規表現で遊んでみてください。それが、応用編の代わりです。ではまた...。

Home / Menu