Perl を使おう! 第6回


[ 目次 / 前頁 ]
前回までに Perl を利用して文書処理を行なったり、プログラムを作成するための準備がかなりできたので、これまでの知識でできることをやってみましょう。

ログの集計

宿題でカウンターをつけ、カウンターのあるページ用のログも取りました。 これを集計してみましょう。

まず、前回の宿題であったページをみてくれた人が使用している種類毎に集計してみます。 ログファイルを見るとわかるように、MSIEとNetscape にはともにMozilla ですね。 MSIE はMSIEであることが記述されているので、これを利用し、まず MSIE であるか、 そうでなければ Mozilla かで3つに分類します。 Windows のNetscape がどのくらいあるかもついでに調べるプログラムにしました。

    ==  brawzstat.pl  ==

Netscape と MSIE 以外にもいくつかのブラウザを使用しているのがわかりました。 他のブラウザについてどんなものがあるかは、表示して確認してみましょう。

    ==  otherbrawz.pl  ==
このままでは、同じブラウザも全て表示されてしまうので大変です。 ブラウザの種類毎に集計し、100以上のものを表示するように修正します。
    ==  otherbrawz1.pl  ==
出力結果
% otherbrawz1.pl

次にログをもっと活用してみましょう。 カウンターをつけたので訪問回数はわかったのですが、現在作成したカウンターは、自分がアクセスした場合もログに残す仕様になっているので、ログファイルを利用してアクセス数を集計するついでに、自分のアクセスするIPアドレスはローカルアクセスとして集計してみましょう。

    ==  logcount.pl  ==
結果
% logcount.pl

また、どんなところから来ているのでしょうか?
    ==  loghostprn.pl  ==

% loghostprn.pl | tail 

全てを表示するCGI

まだまだ、いろいろな工夫が可能ですがそれについてはまたにしましょう。


Perl 言語 - 簡単な表現のために

式修飾子

制御構造として、if, unless などをすでに利用していますが、簡単な文を書く時に { }をいちいち書いたりするのは面倒ですが、Perl には式修飾子というものがあります。 使い方は以下のとおりです。
    exp1 if exp2;     # if (exp2) { exp1; }
    exp1 unless exp2; # unless (exp2) { exp1; }
    exp1 while exp2;  # while (exp2) { exp1; }
    exp1 until exp2;  # until (exp2) { exp1; }
式修飾子はネストできませんが、単純な文は書くのがとても簡単になります。 通常の制御構造と状況に応じて使い分けると良いでしょう。

&&, ||, ?:

&&,||,?: 演算子は制御構造として利用できます。 論理演算子を利用することにより、 exp1 if exp2; を exp2 && exp1; としても実行できます。 これは、論理演算子が前の式を評価した結果、真でなければ後ろの式を評価し真か偽かを判定することを利用します。
    exp1 && exp2;      # if (exp1) { exp2 }
    exp1 || exp2;      # unless (exp1) { exp2; }
    exp1 ? exp2: exp3; # if (exp1) { exp2; } else { exp3; }
これらを利用することによって更に簡略な表現が可能になるでしょう。

起動オプション

Perlを起動する際のオプションについては既に -e オプションなどを利用していますが、知っておくと便利なオプションがいくつかあるので紹介します。
-v オプション
Perl インタプリタのバージョンとパッチレベルを表示する。
-d オプション
スクリプトを Perl デバッガのもとで実行する。
-c オプション
スクリプトの構文チェックのみを行ない、誤りがなければ syntax OK と表示する。
-e オプション
コマンド行から直接プログラムを与え実行できます。
    perl -e 'print "Hello!\n";'
-n オプション
与えられたプログラムが while(<>){..} で囲まれていると解釈し実行する。
    perl -ne 'print "$. : $_"'
    perl -ne 'print if $.==100' log-index
    perl -ne 'print if ($.>=100 && $.<=110)' log-index
    perl -ne 'print "$. : $_" if 100..110;' log-index
    perl -ne '$a[5]=$_; shift @a; print $a[0] if eof' log-index
-p オプション
与えられたプログラムが while(<>){..} で囲まれていると解釈し最後にprintを実行する。与えられたプログラムが while(<>){..} continue {print;} の中にスクリプトがあるものと解釈する。

-i オプション
フィルタプログラムをファイル編集用に使用し、 -iに指定した拡張子を付加した名前でバックアップファイルを作成する。
    % cat mailaddr
    yama@math.ems.okayama-u.ac.jp
    % perl -p -i.bak -e 's/yama/kim/;' mailaddr
    % cat mailaddr
    kim@math.ems.okayama-u.ac.jp
    % cat mailaddr.bak
    yama@math.ems.okayama-u.ac.jp

フォームを利用した CGI プログラミング

前回の鶴亀算の計算をするCGIのところで、フォームからのデータがどのように渡されるか確認のために作った環境変数を表示するプログラムを再び利用してフォームに数字以外が入力された場合について考えましょう。

フォームからのデータ確認フォーム

テキスト1: テキスト2:

例えば text1に"abcdABCD1234",text2に"~!@#$% +-*="\/" をいれて実行した場合、

    test1=abcdABCD12345&text2=%7E%21@%23%24%25+%2B-*%3D%22%5C%2F
が結果として返されました。 ここからわかるように、フォームで入力したデータは "英数字と-*@" 以外の場合に、半角スペース"+" にその他は%で始まる16進数にエンコードされます。 この原理がわかれば、デコードしてから利用すれば良いでしょう。 簡単にこの規則で変換して表示してみましょう。簡単のためテキストボックスは1つにしましょう。

フォームからのデータ確認フォーム(デコードして表示)

テキスト:

    -- ドキュメントソース --
    <FORM ACTION="decparam.cgi" METHOD="GET">
       テキスト: <INPUT TYPE="text" SIZE=30 NAME="text"><P>
       <INPUT TYPE="submit" VALUE="表示">
       <INPUT TYPE="reset" VALUE="入力しなおす">
    </FORM>

    == decparam.cgi ==
さて、このくらいのことができるようになると、現在のページをもっとインタラクティブなものにしてみたくなりますね。 ということで、まずはゲストブックなどを作ってみましょう。

まずは、これまでの知識からHTMLの部分をさっと作ってしまいましよう。 ゲストブックその0 これは、ACTIONを指定していないので、登録ボタンを押しても何も起こりません。

まずは、送れらてきたデータを正しく受け取れるか確認のため、それぞれの値を正しく分割してエンコードしそれらを表示するプログラム(guest1.cgi)を書き、ACTION に指定します。

  ==  guest1.cgi ==
  #!/usr/bin/perl
  print "Content-type: text/plain\n\n";
  $_=$ENV{'QUERY_STRING'};
  tr/+/ /;
  s/%([\da-fA-F][\da-fA-F])/pack("C",hex($1))/eg;
  @param = split('&');
  ($tmp, $name) = split('=',$param[0]);
  ($tmp, $email) = split('=',$param[1]);
  ($tmp, $url) = split('=',$param[2]);
  ($tmp, $mess) = split('=',$param[3]);
  print "Name: $name \nEmail : $email \nURL : $url\nMessage : $mess\n\n";
ゲストブックその1 (ドキュメントソースはブラウザの"ソース表示"メニューで..)

このように、エンコードされたデータをデコードし、それぞれの値を取り出せばよさそうですが、まだ修正の余地があり、問題点もあります。 問題点は、エンコードされた文字列の中に & があった場合に、デコードしてから切り分けると問題が起こります。 & は%26にエンコードされているのでデコードする前に & で分割し、分割されたリストそれぞれについてデコードするように修正します。

    ==  guest2.cgi  ==
ゲストブックその2 これで、どうでしょう。

これでフォームから入力されたデータを分離し、自由に使えるようになったので、実際に利用できるゲストブックを作りましょう。 ここでは、登録されたデータをファイルに書き込み、訪問者リストはHTML形式を利用しメールやリンク等の処理ができるようにします。 確認のため、ファイルに書き込む以外の全ての処理を行なうスクリプトをまず作成します。 また、データの受渡し方法としてGET ではなくPOST を利用する(一言が多い場合にデータの量が多くなるため)ように修正するついでに、後々どちらで渡しても良いように修正します。

    ==  guest3.cgi  ==

    #!/usr/bin/perl
    print "Content-type: text/html\n\n";
    if ($ENV{'REQUEST_METHOD'} eq "POST") {
        read(STDIN, $_, $ENV{'CONTENT_LENGTH'});
    } else {
        $_=$ENV{'QUERY_STRING'};
    }
    @param = split('&');
    foreach $a (@param) {
        ($name,$_) = split('=',$a);
        tr/+/ /;
        s/%([\da-fA-F][\da-fA-F])/pack("C",hex($1))/eg;
        $input{$name}=$_;
    }
    print "<HTML>\n<HEAD>\n  <TITLE>Guest Book</TITLE>\n</HEAD>\n";
    print "<BODY BGCOLOR=FFFFFF>";
    print '以下の内容で登録されました。 ありがとうございました。<PRE>',"\n";
    print '名前 :</TH>',"<TD>$input{'name'}<BR>\n";
    print 'メール  :'," $input{'email'}<BR>\n";
    print "URL     : $input{'url'}<BR>\n";
    print "Message : $input{'message'}</PRE>\n\n";
    print "<HR>\n";
    print "<A HREF=gestlist.html>Guest List</A>\n";
    print "</BODY>\n";
ゲストブックその3

Unix 以外で登録した方にはわかったと思いますが文字が化けます。 というのも、スクリプト内で日本語を記述するようにしており、そこでの文字コードはEUC なのですが、Windows環境から入力した場合、日本語はS-JIS で送られるので、 一つの文書に2つの文字コードが混在することになります。 従ってブラウザの自動判別ではうまくいかないというわけです。 従って日本語の処理が必要ですが、ここではフリーで配布されている jcode.plを利用し日本語コードを全てEUCに統一します。 jcode.pl の詳しい使用法はファイルに記述されています。

    ==  guest4.cgi  ==
    
    #!/usr/bin/perl
    require '../../cgi-bin/jcode.pl';
    print "Content-type: text/html\n\n";
    if ($ENV{'REQUEST_METHOD'} eq "POST") {
        read(STDIN, $_, $ENV{'CONTENT_LENGTH'});
    } else {
        $_=$ENV{'QUERY_STRING'};
    }
    @param = split('&');
    foreach $a (@param) {
        ($name,$_) = split('=',$a);
        tr/+/ /;
        s/%([\da-fA-F][\da-fA-F])/pack("C",hex($1))/eg;
        &jcode'convert(*_,'euc');
        $input{$name}=$_;
    }
    print "<HTML>\n<HEAD>\n  <TITLE>Guest Book</TITLE>\n</HEAD>\n";
    print "<BODY BGCOLOR=FFFFFF>";
    print '以下の内容で登録されました。 ありがとうございました。<P>',"\n";
    print "<CENTER>\n<TABLE BORDER=1 CELLPADDING=4>\n";
    print '<TR><TH> 名 前 </TH>',"<TD>$input{'name'}</TD><TR>\n";
    print '<TR><TH>メール</TH> ',"<TD>$input{'email'}</TD><TR>\n";
    print "<TR><TH>URL   </TH> <TD>http://$input{'url'}</TD><TR>\n";
    print "<TR><TH>Message</TH> <TD>$input{'message'}</TD><TR>\n\n";
    print "</TABLE></CENTER>\n";
    print "<HR>\n";
    print "<A HREF=guestlist.html>Guest List</A>\n";
    print "</BODY>\n";
    open(LIST,">>guestlist.html");
    if ($input{'email'}=~/@/){
        print LIST "<LI><A HREF=\"mailto:$input{'email'}\">$input{'name'}</A> \n";
    } else {
        print LIST "$input{'name'} \n";
    }
    if ($input{'url'}=~/\./) {
        $home="<A HREF=$input{'url'}>$input{'url'}</A>";
    } else {
        $home='なし';
    }
    print LIST 'ホームページ : ',"$home\n";
    if ($input{'message'} eq ""){
        print LIST "<BR>\n\n";
    } else {
        print LIST "<BLOCKQUOTE>$input{'message'}</BLOCKQUOTE>\n\n";
    }
    close(LIST); 
    exit;
ゲストブック完成版

[ 目次 / 前頁 ]


最終更新時刻
By Yoshiro Yamamoto