PaddieのCGIの部屋        Since 2000.04.28

ここではPaddieが作ったCGIスクリプトの紹介やCGI (Webサーバーの設定やPerl) について、わかる範囲で(笑)解説します。
〜Last Modified : 24 May.2000〜


 
Perlってどう書くの?(7)
ではまず基本骨格をコメントだけで書いてみましょう。

---------- 例 8 ここから ----------
#フォームデコード
#もしまともなメッセージがあれば
 {
 #メッセージの漢字コード変換
 #日付取得
 #メッセージ整形
 #記録ファイル読み込み
 #新規メッセージ記録
 #過去のデータを文頭から一行づつ順次再記録(ループ)
  {
  #もしその行が「<HR>」なら
   {#登場回数インクリメント
   }
  #登場回数が19以下なら
   {#その行を記録
   } #19をこえる場合は
   {#ループを抜ける
   }
  }
 }
#記録ファイル読み込み
#HTTPヘッダ出力
#HTML出力
#終了
---------- 例 8 ここまで ----------

全体の流れが大体わかりますよね?

ここに実際に書き込んでいって完成させてみましょう(笑)

当然、始めの一行は「#!/usr/bin/perl」です。
次に、フォームデコード漢字コード変換のために「jcode.pl」と「cgi-lib.pl」を使うことを書いておきます。

require "./cgi-lib.pl";
require "./jcode.pl";
&ReadParse;

フォームデコードというのは、今回のように、フォームで提出されたデータを元の形にデコード(復元)し、扱いやすくするものです。

cgi-lib.plを使うと、今回の例で登場した「name」「mes」の値は、それぞれ$in{'name'}$in{'mes'}に格納されます。

cgi-lib.plを使う利点は、CGIの製作途中でテストしたい場合などに、そのファイルに引数としてフォームデータを渡しても、HTTPで接続した時と同じように全て%inに格納してくれるというところでしょう。

ここで、$in{〜〜}とか%inとかが出てきましたが、これは「連想配列(ハッシュ)」と呼ばれる一種の配列で、かなり便利なものです。

普通の配列ではその要素を$名前[インデックス]で指定しましたが、ハッシュでは$名前{キー}で指定します。
インデックスとキーの違いは、インデックスは数値しか指定できませんが、キーには文字列が使えるというところです。

そして、配列全体を表すのには「@」を使いましたが、ハッシュ全体を表すのには「%」を使うのです。

話は戻りますが、「require "./cgi-lib.pl";」とした後で、「&ReadParse;」とすると、「cgi-lib.pl」内のサブルーチン「ReadParse」によって次の物が分割されてそれぞれ%inに格納されます。
1)CGI(POST)の場合:標準入力「STDIN」
2)CGI(GET)の場合:環境変数「QUERY_STRING」
3)コマンドラインからの起動の場合:引数としてあたえられたもの(@ARGV)

とにかく&ReadParseとすると、フォームで送信されたデータが%inに格納されるんだ、と思って下さい(笑)

今回の例では$in{'name'}$in{'mes'}ができるのですが、以後はそれを使うことにします。

次の「もしまともなメッセージがあれば・・・」というのはifステートメント説明を使います。

ここでは、メッセージ本文だけでなく、名前もなく、さらにメッセージ中に日 本語の文字(全角英数文字を含む) がなければその書き込みを無視するということにします。

ちなみに日本語の文字がない書き込みとは最近はやりの「掲示板荒らし」 の書き込みに対応するものです。
これらには日本語の文字が含まれていないことがほとんどなので、排除することで簡単に変な書き込みをはじく ことができるというわけです。

メッセージ本文に日本語の文字が含まれているかどうかはjcode.plを使うと簡単に判別できます。
まずメッセージ本文は%inに格納されていて、そのままではjcode.plに渡せませんから 「$mes = $in{'mes'};」としてスカラー変数$mesにそのまま代入します。
次にこの「$mes」に日本語の文字が含まれるかどうかを判定すれば良いわけですが、それはこのようにして行います。
$code = &jcode'getcode(*mes);
これで、日本語の文字が含まれていればその文字コードを判別して 「euc」「jis」「sjis」等の値が「$code」に代入されます。
しかし、日本語の文字が含まれないときは「undef(未定義)」が代入されますので、$codeは空の状態になります。
つまり、 $codeに何かの値がセットされていなければ日本語の文字がないということになります。

ですから、ifステートメントで評価する条件式は「$in{'name'} && $in{'mes'} && $code」と なります。
これで$in{'name'}および$in{'mes'}の両方ともに(0以外の)値があり、$in{'mes'}に日本語の文字が ある時に「真」となり、続くステートメントブロックが実行されます。

$mes = $in{'mes'};
$code = &jcode'getcode(*mes);
if ($in{'name'} && $in{'mes'} && $code)

で、漢字コード変換をします。しかし、%inに格納されたままではjcode.plで漢字コード変換ができないので、それぞれの値を別のスカラー変数に代入してから、そちらを変換し、 続いて日付の取得とメッセージの整形をします。

$name = $in{'name'};
$mes = $in{'mes'};
&jcode'convert (*name,"euc");
&jcode'convert (*mes,"euc");
($min,$hou,$day,$mon,$yea) = (localtime(time))[1..5];
$mon++;
$yea += 1900;
$date = sprintf("%4d/%.2d/%.2d %.2d:%.2d",$yea,$mon,$day,$hou,$min);
$mes =~ s/\n/<BR>\n/g;
$mes .= "\n"
$mes = "<HR>\n$name さん $date<BR>\n$mes";

スライスで[1..5]となっているのは[1,2,3,4,5]と同じ意味です。

さて、$mes =~ s/\n/<BR>\n/g;というのがでてきました。
=~」という変なのはパターンマッチング演算子説明、「s/(A)/(B)/」というのは置換演算子説明です。この二つを組み合わせて使うことによって、対象文字列内の(A)という文字列を(B)という文字列に置き換えることができます。後に続く「g」というのは、対象文字列内の該当個所を全て置換するという意味のオプションです。

ここではスカラー変数$mes内の全ての改行文字(\n)を「<BR>\n」に変換させ、続いて$mesに改行コードを加えています。 「=」は代入演算子ですが、「$mes .= "〜〜"」という風に使うと、「$mes = $mes . "〜〜"」と同じ意味になり ますので、これによりスカラー変数$mesに右辺の値を追加代入できます。

そして、次の行では記録用にHTMLのタグを付けたり名前を組み込んだりという整形をしています。 その他、この内容については、もう説明の必要はないですよね?

次は、記録ファイルを読み込んで、新しいメッセージでそのファイルを上書きします。

open (IN,"./guestbook.txt");
@lines = <IN>;
close IN;
open (OUT,">./guestbook.txt");
print OUT $mes;

これは簡単ですね(^^)
記録ファイルをファイルハンドル「IN」で読み込みモードで開いて、全行を配列「@lines」に格納し、ファイルを閉じています。
続いて同じファイルを上書きモード(ファイルハンドル「OUT」)で開き、新しいメッセージを書き込んでいます。

あとで古いメッセージを書き込んでいく処理が残っていますので、ファイルは開いたままにしてあります。

さぁ、次が問題です。
過去のデータを文頭から一行づつ順次再記録って、どうするのでしょうか?

これは実はすごく簡単なんです。さきほど、元の記録ファイルの中身を「@lines」に全て代入しましたよね?

すると、一行目が$lines[0]、二行目が$lines[1]・・・と言う風に勝手になってくれているので、順番にやっていけばいいのです。

でも、いちいち全て書いていたのでは面倒ですし、それぞれのメッセージは行数が同じとは限らないので何行分処理すればいいのかわかりません

そこで登場するのが「foreach(フォーイーチ)ループ説明です。これはかなり便利です。

たとえば「foreach $temp (@lines)」としておけば、配列@linesの各要素を順次スカラー変数$tempに代入しながら後に続くステートメントブロックを実行してくれるのです。

代入先のスカラー変数を指定しなければ、勝手に特殊変数$_に代入してくれます。

ですから、ここではこういう風にしてみました。

foreach (@lines)
 {
 if ($_ eq "<HR>\n")
  {$number++;}
 if ($number <= 19)
  {print OUT $_;}
 else
  {last;}
 }
close OUT;

メッセージの記録される個数には制限を掛けることにしましたので、その判定をしながら書き込んでいかなければなりません。

こういうときはやっぱり「if」を使います。

その行が「<HR>\n」(個数判別用のキーワードですね(^^) )なら$numberをインクリメント(1を足す)します。
で、それが19以下の時はまだ書き込んでもいいメッセージですから記録ファイルに書き込みます。

20以上だったら何もせず「last説明foreachループから抜けます

ループが終了したら記録ファイルを閉じます。

続いて表示部に入るのですが、記録ファイルを再度読み込む前に配列@linesをクリアしておきます。配列をクリアするには空のリストを代入すればいいのです。

@lines = ();

読み込みは先ほどと全く同じです。

open (IN,"./guestbook.txt");
@lines = <IN>;
close IN;

いよいよ出力になりました。始めにHTTPヘッダを出力します。

print "Content-type: text/html; Charset=euc-jp\n\n";

HTML本体を出力します。ここでは「ヒアドキュメント説明という方法で出力してみました。

ヒアドキュメントとは、「<<キーワード;」で始まるもので、キーワードだけの行が来るまでの間、改行文字も含めて代入するというもので、こういう風に使いました。

print <<EOF;
<HTML>
<HEAD>
<TITLE>GuestBook</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<FONT SIZE="5">GUEST BOOK!</FONT><BR>
<FORM ACTION="./guestbook.cgi" METHOD="POST">
お名前:<BR>
<INPUT TYPE="TEXT" NAME="name" VALUE=""><BR>
メッセージ:<BR>
<TEXTAREA ROWS="5" COLS="70" WRAP="HARD" NAME="mes"></TEXTAREA><BR>
<INPUT TYPE="SUBMIT" VALUE="送信!">
 <INPUT TYPE="RESET" VALUE="書き直し"><BR>
</FORM>
@lines
<HR>
</BODY>
</HTML>
EOF

こうすることにより、<HTML>の行から</HTML>の行まで一括して出力できます。

また、途中に「@lines」と書かれた行がありますが、配列を出力しようとすると、その要素が順に書き出されるため、単に「@lines」と書くだけで自動的に全ての行が埋め込まれます。

そして、スクリプトファイルの最後には「exit」を付けて終了させておきましょう。

結局、できあがったファイルは、こうなります。


#!/usr/bin/perl
require "./cgi-lib.pl";
require "./jcode.pl";
#フォームデコード
&ReadParse;
#もしまともなメッセージがあれば
$mes = $in{'mes'};
$code = &jcode'getcode(*mes);
if ($in{'name'} && $in{'mes'} && $code)
	{
	$name = $in{'name'};
	$mes = $in{'mes'};
	#メッセージの漢字コード変換
	&jcode'convert(*name,"euc");
	&jcode'convert(*mes,"euc");
	#日付取得
	($min,$hou,$day,$mon,$yea) = (localtime(time))[1..5];
	$mon++;
	$yea += 1900;
	$date = sprintf("%4d/%.2d/%.2d %.2d:%.2d",$yea,$mon,$day,$hou,$min);
	#メッセージ整形
	$mes =~ s/\n/<BR>\n/g;
	$mes .= "\n";
	$mes = "<HR>\n$name さん $date<BR>\n$mes";
	#記録ファイル読み込み
	open (IN,"./guestbook.txt");
	@lines = <IN>;
	close IN;
	#新規メッセージ記録
	open (OUT,">./guestbook.txt");
	print OUT $mes;
	#過去のデータを文頭から一行づつ順次再記録(ループ)
	foreach (@lines)
		{
		#もしその行が「<HR>」なら
		if ($_  eq "<HR>\n")
			{#登場回数インクリメント
			$number++;
			}
		#登場回数が19以下なら
		if ($number <= 19)
			{#その行を記録
			print OUT $_;
			} #19をこえる場合は
			else
			{#ループを抜ける
			last;
			}
		}
	close OUT;
	}
#記録ファイル読み込み
@lines = ();
open (IN,"./guestbook.txt");
@lines = <IN>;
close IN;
#HTTPヘッダ出力
print "Content-type: text/html; Charset=euc-jp\n\n";
#HTML出力
print <<EOF;
<HTML>
<HEAD>
<TITLE>GuestBook</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<FONT SIZE="5">GUEST BOOK!</FONT><BR>
<FORM ACTION="./guestbook.cgi" METHOD="POST">
お名前:<BR>
<INPUT TYPE="TEXT" NAME="name" VALUE=""><BR>
メッセージ:<BR>
<TEXTAREA ROWS="5" COLS="70" WRAP="HARD" NAME="mes"></TEXTAREA><BR>
<INPUT TYPE="SUBMIT" VALUE="送信!">
 <INPUT TYPE="RESET" VALUE="書き直し"><BR>
</FORM>
@lines
<HR>
</BODY>
</HTML>
EOF
#終了
exit;


ここで作った作例は、そのままの状態で実際に動作させてありますので、適当に書き込んでみてください。  >> ここにおいてあります。 <<

また、実際にファイルをダウンロードしたい方は下の欄にメールアドレスを記入してダウンロードボタンを押して下さい。
Eメール: 
ダウンロードしたファイルは漢字コードをEUC、改行コードをLFにして保存してください。


Perlってどう書くの?(6)
CGIの部屋へ戻る

・・・ つづく ・・・
 


Copyright 1999-2000 Paddie
私宛のメールは"paddie@paddie.com"まで。

// TOPページ  // 掲示板  // MEMOちゃ  // CGIの部屋 //