超簡単掲示板
では実際に簡単な掲示板スクリプトを作りながら、Perlの構文等について詳しく見て行きましょう。スクリプトの名前は、取り敢えずkeijiban.cgi としときましょう。jcode.plは、こちらからダウンロードできます。txt形式でアップしていますので、右クリックでダウンロードしてからファイル名をjcode.plに変更して下さい。他に記事保存用のkeijiban.txt が必要ですから、テキストエディタで適当に作ってやって下さい。すべてのファイルが用意できたら、CGIを許可されているディレクトリ内にFTPで転送します。各ファイルのパーミッションは以下の通りです。
keijiban.cgi ・・・755 keijiban.txt ・・・666 jcode.pl ・・・644
この掲示板の機能の概略
出来るだけシンプルな物にしたいと思っていますが、一応最大記録件数は設定しておくつもりです。Web上での記事の削除等の管理機能は付けません。直接FTPソフトでログファイルkeijiban.txt を操作して行って下さい。又、各記事への返信機能も付けませんので、一つずつ別個に書き込みをしてやって下さい。今回は、飽くまでも基本機能のみの装備です。
スクリプトの内容
では実際のスクリプトの内容を検証して行きましょう。各関数の使い方などはその都度説明していますので、実際の処理の流れの中でその働きを理解するようにして下さい。
初期設定 #!/usr/local/bin/perl Perlのパス指定・・・加入しているプロバイダのPerlが実行できるパスを指定します。通常は「#!/usr/local/bin/perl」又は「#!/usr/bin/perl」が一般的です。 (この後、必ず1行空けること。) #ライブラリの呼び出し 日本語変換ライブラリの宣言・・・CGIプログラム本体と同じディレクトリにある場合は、「'jcode.pl'」又は「'./jcode.pl'」でいいですが、一つ上のディレクトリにある場合は、きちんと相対パスで「'../jcode.pl'」としなければなりません。 require 'jcode.pl'; #CGIスクリプトを置いてあるURL 一番最初の行以外で頭に「#」がある行は、全てコメントと見なされます。スクリプト実行時には無視されます。 $reload = 'http://***.***.***.jp/~*****/cgi-bin/keijiban.cgi' コメント送信後リロードしますので、変数$reloardにCGI設置場所のURLを格納します。 ★スカラー変数「$で始まる」・・・「スカラー型」は、1つの変数(文字列、数値など)を格納することが出来ます。 #ホームページのタイトル $bbs_title = '超簡単掲示板'; #メッセージを格納するファイル $datafile = 'keijiban.txt'; 相対パスで指定します。例:'./***.txt' '../***.txt' #コメント最大記憶数 $max = 50; 記事の肥大化を防ぐ為に設定しておきます。 メインの設定 #日付・時刻をシステムから取得し、フォーマットを整える ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); ★time関数・・・UTC(協定世界時間)1970年1月1日00:00:00からの秒数を返します。通常このtime関数で得られた時刻をgmtime関数、またはlocaltime関数に引数として渡し、変換します。 ★localtime関数「<ローカル時間> = localtime[time形式の値]」・・・戻り値はローカル時間が返ってきますが、この時スカラー型の変数で受け取るか、配列で受け取るかで戻ってくる値の形式が異なります。 スカラー型・・・以下の書式の値が戻ってきます。 wdy mon day hh:mm:ss yyyy 「曜日、月、日、時分秒、年」 配列型・・・以下の書式の値が戻ってきます。 「秒、分、時、日、月、年、曜日、元旦からの日数、夏時間の有無」 $year += 1900; ◆$yearの値は西暦から1900を引いた数字となっていますので、正しく西暦を表示するために1900を足してやります。尚、「$year += 1900」は、「$year = $year + 1900」の省略形です。 #取得した数値を二桁に統一 ★sprintf関数「<フォーマット済み文字列> = sprintf(フォーマット定義,文字)」・・・フォーマットの定義に従って文字列のフォーマットを行い返します。 $month = sprintf("%02d",$mon + 1); ◆$monの値は「0〜11」の数値で返されるので、実際の月データにする為に1を足してやります。 $mday = sprintf("%02d",$mday); ★フォーマット定義・・・「%」から始まり、フラグと型の指定で定義されます。「%02d」は、右詰2桁の10進法で、数値の前に'0'が並んでいることを表します。「0」はフラグで、指定した文字数になるように数値の前を'0'で埋めることを、「d」は型で、10進法での出力を表します。 $hour = sprintf("%02d",$hour); $min = sprintf("%02d",$min); #フォーマットを整える $date_now = "$year年$mon月$day日 $hour時$min分"; 「"」ダブルクォーテーションで囲まれた定数の中の変数は自動で展開されます。 #投稿フォームからのデータを取得 if ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN, $QUERY_DATA, $ENV{'CONTENT_LENGTH'});
} else { $QUERY_DATA = $ENV{'QUERY_STRING'}; }GETとPOSTでデータの取得法が異なるため、どちらでも取得できるようにします。
◆$ENV{'REQUEST_METHOD'}・・・<FORM>タグのMETHODアトリビュートが返されます。'GET'/'POST'◆$ENV{'CONTENT_LENGTH'}・・・POSTクエリーにより送られてきた文字数がバイト単位で格納されています。 ◆$ENV{'QUERY_STRING'}・・・GETクエリーにより送られてきたフォームからのデータが格納されています。 #データを分解してペアにする @pairs = split(/&/,$QUERY_DATA);
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);配列型の変数は、変数名の前に「@」を付けて表します。また、フォームから送られてきたデータは、「name=***&email=***&comment=***」のように「&」で区切られているので、このデータを分解してペアにします。 ★split関数「<分割された文字列配列> = split[区切りパターン,分割対象文字列]」・・・文字列を任意の分割パターンに従って分割を行い、その結果を返します。 ★foreach関数「foreach 変数 (list) {ブロック;}」・・・listの1番から順に変数に代入し、ブロックを実行します。listが終了した時ループから抜け出します。 #不必要な文字を削除/デコードし、入力されたタグを無効化 $value =~ tr/+/ /; ◆変換演算子・・・文字列の変換や削除を行います。変換演算子の書式は以下の通りです。 tr/ 変換対象の文字リスト / 変換する文字リスト / $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; 変換された特殊文字を元に戻します。 $value =~ s/</</g; ◆置換演算子・・・正規表現で表されたパターンにマッチした文字列を、置換します。置換演算子の書式は以下の通りです。 $value =~ s/>/>/g; $value =~ s/\n//g; s/ 正規表現 / 置換文字列 /[修飾子] $value =~ s/\,/,/g; ◆パターン結合演算子「=~」・・・スカラー変数(式)とマッチ演算子、置換演算子、変換演算子を結合します。
修飾子
意味
e 置換文字列を式とみなし実行する g マッチするもの全てをみつける i 大文字と小文字を区別しない m 文字列を複数行として扱う o 変数展開を1回だけ行う s 文字列を単一行として扱う x 拡張正規表現を使用する &jcode'convert(*value,'sjis'); 文字コードをsjisに変換します。 $FORM{$name} = $value; 分解したデータを連想配列に格納 } if (!open(NOTE,"$datafile")) { &error(bad_file); }
@DATA = <NOTE>;データファイルを読み込みモードでオープンし、全てのデータを配列変数「@DATA」に格納します。
(!open.....)の「!」は、否定の意味。close(NOTE); 一旦開いたファイルは必ず閉じる事。 #処理の分岐 if ($FORM{'action'} eq 'regist') {®ist;} 「eq ***」は、「***であれば」の意味。 else {&html;} HTMLドキュメントを生成 sub html { print "Content-type: text/html\n\n"; 「\n\n」で下の<html>との間に1行空けている。 print "<html><head>\n"; print "<title>" . $bbs_title . "</title></head>\n"; ホームページのタイトルを設定。 print "<body bgcolor=#FFFFDD text=#000000 link=#FF0000 vlink=#FF0000>\n"; バック、文字、リンク等の色を設定。 print "<form action=keijiban.cgi method=POST>\n"; フォームの設定。 print "<input type=hidden name=action value=regist>\n"; print "<center><font size=6>" . $bbs_title . "</font></center>\n"; ページの題目を表示。<center>は中央揃え。 print "<center><font color='red'>お名前とコメントは、必ずご記入下さい。尚、記事の最大記録数は50件です。</font></center>\n"; 注意書きを表示。 print "<table border=0 cellspacing=1>\n"; ◆table border=0・・・テーブルの境目をなくしています。 print "<tr><td align=right>お名前</td>\n"; ◆<tr></tr>・・・表環境内における行を定義します。新しい行が始まるごとにこのタグを使います。 print "<td><input type=text size=29 name=name></td></tr>\n"; ◆<td></td>・・・<tr>タグで定義された行の中で、セルの属性をデータとして指定します。 print "<tr><td align=right>E-mail</td>\n"; ◆align=right・・・右揃え処理です。 print "<td><input type=text size=29 name=email></td></tr><tr>\n"; print "<td align=right>HomePage</td>\n"; print "<td><input type=text size=50 name=HP></td></tr>\n"; print "<tr><td align=right>題名</td>\n"; print "<td><input type=text size=50 name=subject></td></tr>\n"; print "<tr><td align=right>コメント</td>\n"; print "<td><textarea name=comment rows=4 cols=68></textarea></td></tr>\n"; ◆rows・・・縦の行数を指定します。 ◆cols・・・横の幅を指定します。 print "<tr><td align=center colspan=2><input type=submit value=書き込み>"; ◆colspan=2・・・セルを横に2つ結合します。 print " <input type=reset value=取り消し></td></tr>\n"; print "</table></form>\n"; foreach $line (@DATA) {
($date,$name,$email,$HP,$subject,$comment) = split(/\,/,$line);$comment =~ s/\r/<br>/g; コメント内での「\r」を<br>に変換し、改行を有効にします。 print "<hr>\n"; 水平線<hr>を入れます。 print "<font color='blue' size=4><b>$subject</b></font>\n"; <b></b>は太字の意味。 if ($email ne "") { 「ne ***」は「***でなければ」の意味。 print "<a href=mailto:$email><strong> $name</strong></a>\n"; <strong></strong>は強調の意味。 } else { print "<strong> $name</strong>\n"; } if ($HP ne "") { print "<a href=$HP target=_top> HomePage</a>\n"; ◆targetオプション
「_blank」・・・新しいウィンドウを別に一枚生成し、そこにファイルを表示します。
「_self」・・・今と同じフレームまたはウィンドウにファイルを表示します。
「_parent」・・・リンクタグのあったフレームの親ウィンドウにファイルを表示します。
「_top」・・・すべてのフレームを破棄し、ウィンドウに表示します。} print "<font size=-1> $date"; 日付「$date」を表示 print "<blockquote>$comment</blockquote>\n"; <blockquote></blockquote>は、何らかの文章を引用する時に使います。このタグで囲むと、その前後は自動的に改行されます。 } print "<hr>\n"; 水平線<hr>を入れる print "</body></html>\n"; exit; } 記事をファイルに書き込む sub regist { if ($FORM{'name'} eq '') { &error(bad_name); } 書き込まれたフォームデータに「name」と「comment」がなければ、エラー処理ルーチンに分岐します。 if ($FORM{'comment'} eq '') { &error(bad_comment); } $count = @DATA; 総データ数をカウント if ($count > $max) { pop (@DATA); } ★pop関数「pop(配列変数)」・・・配列変数の最後の要素を取り除いて返します。この時、配列の要素は1つ減ります。 $value = "$date_now\,$FORM{'name'}\,$FORM{'email'}\,$FORM{'HP'}\, $FORM{'subject'}\,$FORM{'comment'}\,\n"; フォームからの値を変数$valueに格納。 unshift (@DATA,$value); ★unshift関数「unshift <配列>, <追加する要素>」・・・配列の先頭に要素を追加します。 if (!open(NOTE,">$datafile")) { &error(bad_file); } ファイルを書き込みモードでオープンして変数@DATAの全データをファイルハンドル<NOTE>に書き出します。 print NOTE @DATA; close(NOTE); print "Location: $reload" . '?' . "\n\n"; } エラー処理ルーチン sub error { サブルーチンの宣言 $error = $_[0]; if ($error eq "bad_file") { $msg = 'ファイルのオープン、入出力に失敗しました。'; } elsif ($error eq "bad_name") { $msg = 'お名前が記入されていません。'; } elsif ($error eq "bad_comment") { $msg = 'コメントが記入されていません。'; } else { $msg = '原因不明のエラーで処理を継続できません。'; } print "Content-type: text/html\n\n"; print "<html><head><title>" . $bbs_title . "</title></head>\n"; print "<body bgcolor=#FFFFDD text=#000000 link=#FF0000 vlink=#FF0000>\n"; print "<center><h2>エラーです!</h2>\n"; print "やり直して下さい。<hr>\n"; print "<b>" . $msg . "</b></center>\n"; print "</body></html>\n"; exit; } スクリプトの全体像
分かり易いように、上のサンプルスクリプトのソース全体を示します。実際には処理の流れがつかみ易いように、タブで字下げをして処理の塊を区別できるようにしています。
#!/usr/bin/perl
# ↑あなたが加入しているプロバイダの「perl」言語が使用できるパスを指定します。
# 一般的には「#!/usr/local/bin/perl」か「#!/user/bin/perl」で大丈夫。
require '../jcode.pl';
$reload = 'http://www.komonet.ne.jp/~perl/cgi-bin/keijiban/keijiban.cgi';
$bbs_title = '超簡単掲示板';
$datafile = 'keijiban.txt';
$max = 50;
#==============初期設定が必要なのはここまでです=========================
($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
$mon = sprintf("%02d", $mon + 1);
$day = sprintf("%02d", $day);
$hour = sprintf("%02d", $hour);
$min = sprintf("%02d", $min);
$date_now = "$year年$mon月$day日 $hour時$min分";
if ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN, $QUERY_DATA, $ENV{'CONTENT_LENGTH'});
} else { $QUERY_DATA = $ENV{'QUERY_STRING'}; }
@pairs = split(/&/,$QUERY_DATA);
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ s/</</g;
$value =~ s/>/>/g;
$value =~ s/\n//g;
$value =~ s/\,//g;
&jcode'convert(*value,'sjis');
$FORM{$name} = $value;
}
if (!open(NOTE,"$datafile")) { &error(bad_file); }
@DATA = <NOTE>;
close(NOTE);
if ($FORM{'action'} eq 'regist') {®ist;}
else {&html;}
#========================HTMLドキュメントを生成=============================
sub html {
print "Content-type: text/html\n\n";
print "<html><head>\n";
print "<title>" . $bbs_title . "</title></head>\n";
print "<body bgcolor=#FFFFDD text=#000000 link=#FF0000 vlink=#FF0000>\n";
print "<form action=keijiban.cgi method=POST>\n";
print "<input type=hidden name=action value=regist>\n";
print "<center><font size=6>" . $bbs_title . "</font></center>\n";
print "<center><font color='red'>お名前とコメントは、必ずご記入下さい。尚、記事の最大記録数は50件です。</font></center>\n";
print "<table border=0 cellspacing=1>\n";
print "<tr><td align=right>お名前</td>\n";
print "<td><input type=text size=29 name=name></td></tr>\n";
print "<tr><td align=right>E-mail</td>\n";
print "<td><input type=text size=29 name=email></td></tr>\n";
print "<tr><td align=right>HomePage</td>\n";
print "<td><input type=text size=50 name=HP></td></tr>\n";
print "<tr><td align=right>題名</td>\n";
print "<td><input type=text size=50 name=subject></td></tr>\n";
print "<tr><td align=right>コメント</td>\n";
print "<td><textarea name=comment rows=4 cols=60></textarea></td></tr>\n";
print "<tr><td align=center colspan=2><input type=submit value=書き込み>";
print " <input type=reset value=取り消し></td></tr>\n";
print "</table></form>\n";
foreach $line (@DATA) {
($date,$name,$email,$HP,$subject,$comment) = split(/\,/,$line);
$comment =~ s/\r/<br>/g;
print "<hr>\n";
print "<font color='blue' size=4><b>$subject</b></font>\n";
if ($email ne "") {
print "<a href=mailto:$email><strong> $name</strong></a>\n";
} else { print "<strong> $name</strong>\n"; }
if ($HP ne "") {
print "<a href=$HP target=_top> HomePage</a>\n";
}
print "<font size=-1> $date</font>\n";
print "<blockquote>$comment</blockquote>\n";
}
print "<hr>\n";
print "</body></html>\n";
exit;
}
#=======================記事をファイルに書き込むサブルーチン=================
sub regist {
if ($FORM{'name'} eq "") { &error(bad_name); }
if ($FORM{'comment'} eq "") { &error(bad_comment); }
$count = @DATA;
if ($count > $max) { pop (@DATA); }
$value = "$date_now\,$FORM{'name'}\,$FORM{'email'}\,$FORM{'HP'}\,$FORM{'subject'}\,$FORM{'comment'}\,\n";
unshift (@DATA,$value);
if (!open(NOTE,">$datafile")) { &error(bad_file); }
print NOTE @DATA;
close(NOTE);
print "Location: $reload" . '?' . "\n\n";
exit;
}
#============================エラー処理ルーチン=============================
sub error {
$error = $_[0];
if ($error eq "bad_file") { $msg = 'ファイルのオープン、入出力に失敗しました。'; }
elsif ($error eq "bad_name") { $msg = 'お名前が記入されていません。'; }
elsif ($error eq "bad_comment") { $msg = 'コメントが記入されていません。'; }
else { $msg = '原因不明のエラーで処理を継続できません。'; }
print "Content-type: text/html\n\n";
print "<html><head><title>" . $bbs_title . "</title></head>\n";
print "<body bgcolor=#FFFFDD text=#000000 link=#FF0000 vlink=#FF0000>\n";
print "<center><h2>エラーです!</h2>\n";
print "やり直して下さい。<hr>\n";
print "<b>" . $msg . "</b></center>\n";
print "</body></html>\n";
exit;
}*上の内容をこのまま複写しても動きません。何故なら、各コードの行頭のスペースが半角でないからです。ご注意下さい。
次へ 超簡単アクセスカウンター |