超簡単掲示板

 では実際に簡単な掲示板スクリプトを作りながら、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/</&lt;/g; ◆置換演算子・・・正規表現で表されたパターンにマッチした文字列を、置換します。置換演算子の書式は以下の通りです。
$value =~ s/>/&gt;/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') {&regist;} 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/</&lt;/g;
 $value =~ s/>/&gt;/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') {&regist;}
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;
}

 *上の内容をこのまま複写しても動きません。何故なら、各コードの行頭のスペースが半角でないからです。ご注意下さい。

 

HOME

次へ 超簡単アクセスカウンター