さ み だ れ 雑 記 | 13 | 1999年7月21日 |
CGIでPerlのお勉強(その2)
さー、気合いを入れて、プログラムの解説です。実際に、このページで動いているやつです。
アンケートかきかき の方。まず、流れ
1:「アンケートかきかき」の画面から入力する。
この画面を表示するのが、en.htmlです。ここで、<form>というタグを使います。
みなさんのブラウザが入力された情報をインターネットを介して、WWWサーバに伝えます。
2:ブラウザから受け取った情報を、CGI/Perlで処理します。
具体的にはen.cgiというファイルに記述されたプログラムが働きます。
送られてきた情報をen.csvというファイルに書き込みます。
エラーが起こったら、エラーの、成功したら、書き込み完了のメッセージを送ります。
3:みなさんのブラウザがメッセージを受け取って表示します。
というのが大筋です。
では、いざ。
1.en.html
まず「アンケートかきかき」画面を表示させているen.htmlから。(enはアンケート[フランス語]のenです)。
<html>
<head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <title>アンケート</title> </head> <body bgcolor="#CCCCCC"> <h1><font color="#CC33CC"><font size=+4>アンケートかきかき</font></font></h1> <font color="#FF0000">非公開</font>ですので、思うままに書いて下さい。 <p>公開したい!という方は、「発言かきかき」にどうぞ。<br> <hr> <form ACTION="en.cgi" METHOD="post">お名前(本名でなくて結構です)<input TYPE="text" SIZE=40 NAME="yourname"> <p>ページの更新をメールでお知らせましょうか? <input TYPE="radio" NAME="sendem" VALUE="sem">希望する<input TYPE="radio" NAME="sendem" VALUE="noem">希望しない <p>どのようにこのページを知りましたか? <select NAME="howcome"
SIZE=1><option>不明<option>作者から直に<option>友人・知人から<option>サーチエンジンで<option>リンクから<option>その他</select>
</body>
|
実際のものより、項目数を減らしています。だって煩雑なんだもん。
HTMLの基本的なことは、「講座」の方を参照してもらうとして、
<form ACTION="en.cgi" METHOD="post">お名前(本名でなくて結構です)<input
TYPE="text" SIZE=40 NAME="yourname">
のところが、
と表示されます。
<form></form>タグを使います。終タグの</form>は最後に一回でいいです。
ACTION="***.CGI"で情報を渡す cgi ファイルを指定します。ACTION="mailto:***"とすると、メールで送ることも出来るそうですが、日本語は文字化けする(読めるようにするソフトも存在するらしい)、とか、IEの3.0ではうまくいかないとかあるみたいです。
METHODは渡す方法で、postと、getがあるそうです。通常psotが使われるそうです。
<imput>は終タグ(</imput>)なしで使います。
TYPEは9種類から選べますが、網羅的な説明は出来ません。(まだ能力がない)
ここで使った"text"は上のようなテキスト入力用の空欄を開きます。
SIZEはテキスト入力用の空欄のサイズです。省略すると20が指定されます。
次のNAMEは、「フィールド名」という用語があるみたいですが、ここでは”yourname”としていますので、yourname=K's Kornerってな感じで送られるわけです。(感じですよ、感じ)
なお「フィールド名」は原則、任意の名前が付けられますが、nameは不可です。私が実際やって動きませんでした。(「入門」本のサンプルがmynameとなっていて、「何で私の名前なの?名前でいいだろ」とnameにしたら動かない・・・。)
<p>ページの更新をメールでお知らせましょうか? <input TYPE="radio" NAME="sendem" VALUE="sem">希望する<input TYPE="radio" NAME="sendem" VALUE="noem">希望しない
のところが
と表示されます。
TYPE="radio" で上のようなチェックボタンが表示されます。選択されたVALUE=の値が送られます。っていう荒い説明でわかりますよね。もちろん3つ以上から選択させることもできます。
なお、VALUE=" "の後に続けて、CHECKED とだけ書いておくと、チェックされたボタンにチェックをつけて表示されるそうです。
ブラウザでチェックなしで送ったら、何も送られないというより、何もはいってないよ、と送られます。
<p>どのようにこのページを知りましたか? <select NAME="howcome" SIZE=1><option>不明<option>作者から直に<option>友人・知人から<option>サーチエンジンで<option>リンクから<option>その他</select>
のところが
と表示されます。
VALUE="**" を省略する、つまり
<p><input TYPE="submit"><input TYPE="reset"></form>
は
と表示されます。説明の必要もないでしょうが、submitで送られ、resetで初期化されます。
なお、普通HTMLのタグは大文字でも小文字でもいいんですが、METHOD, TYPE等この例で大文字になっている部分は大文字でないと駄目なんだそうです。
2.en.cgi
これも、実際のファイルより簡略化してます。「できのいいプログラム」=「素人にはよくわからんプログラム」みたいなとこがあって、「いっぺんに色々されると、頭が割れる!!!」ということで・・・・・。エラーチェックのとことか省略しています。でもちゃんと動くよ。(エラーが起こったら動かない・・・じゃなくてエラーが起こっても、一見、動くから困る)
ま、最初ざっと見て下さい。元になったguestadd.cgi に比べたら軽いもんです。
あ、くどいですが、全体の流れは頭に入ってますね。1のen.htmlで表示された画面で入力された情報がこのプログラムにやってきて、このプログラムはその情報を処理し、en.csvというファイルに書き込み、書き込み完了をブラウザに返すという段取りでした。
# This cgi.file is based on
# guestadd.cgi Version 1.0.5 # Copyright (C) 1997 by Hiroshi Yuki # All rights reserved. # 結城浩 <hyuki@st.rim.or.jp> # http://www.st.rim.or.jp/~hyuki/ # # 漢字ライブラリの読み込み require "jcode.pl"; # 初期化
# データファイルのオープン
# ファイルのしっぽに行く
# データファイルの書き込み開始 # 名前
# 更新連絡
# ホームページをどう知ったか
# コメント
# 記入日時
# 以上で新規データの書き込み終了 # データファイルのクローズ
# 書き込み成功をブラウザに伝える print "Content-type: text/html\n\n";
# 終了
# 以下、サブルーチン # 現在日時を文字列化する
# フォームからの情報を連想配列 %form に入れる
|
じゃ、ちょっとずつ。
# This cgi.file is based on
# guestadd.cgi Version 1.0.5
# Copyright (C) 1997 by Hiroshi Yuki
# All rights reserved.
# 結城浩 <hyuki@st.rim.or.jp>
# http://www.st.rim.or.jp/~hyuki/
# で始まる行はコメントです。何でもPerlの処理系によっては、;#と最初にセミコロンをつけないと駄目とかあるそうです。
guestadd.cgiを元にしたので、元の作者を明示してます。ぜひ元のcgiと比べて下さい。だいぶ改造してます。
# 漢字ライブラリの読み込み
require "jcode.pl";
簡単にいえば、日本語処理をするための「おまじない」って部分です。って書いたらjcode.plの作者が泣くかなあ。
jcode.pl の最初の部分です。
package jcode;
;######################################################################
;#
;# jcode.pl: Perl library for Japanese character code conversion
;#
;# Copyright (c) 1995,1996,1997 Kazumasa Utashiro <utashiro@iij.ad.jp>
;# Internet Initiative Japan Inc.
;# 3-13 Kanda Nishiki-cho, Chiyoda-ku, Tokyo 101, Japan
;#
;# Copyright (c) 1992,1993,1994 Kazumasa Utashiro
;# Software Research Associates, Inc.
;#
プログラム作るのに「効率」っていうか、決まった処理をする部分は纏めて、「部品化」すると楽ですよね。一つのファイルの中では、次にすぐ出てきますが、サブルーチンと呼ばれる、「部品」というか「ユニット」を利用します。でもサブルーチンはそのプログラムの中でしか利用できません。ということで、外部から呼び出して使う「部品」を「ライブラリ」と呼びます。.libとか.pl(perlのpl)という拡張子がついているようです。それを呼び出す「呪文」がrequire:「要求する」。
う、ここは簡単。(なお同一ディレクトリだからパスは省略してます。(「パス?」と思った人は「HTML講座」参照して下さい。)
次
# 初期化
&init_form(sjis);
もうでちゃった。サブルーチン。&がサブルーチンを示す記号です。(sjis)はそれをシフトJISで処理してくれ、っていうことです。そのサブルーチンは後ろにあるんですが、これです:
# フォームからの情報を連想配列 %form に入れる
# Copyright (C) 1997 by Hiroshi Yuki.
sub init_form {
local($query, @assocarray, $assoc, $property, $value,
$charcode, $method);
$charcode = $_[0];
$method = $ENV{'REQUEST_METHOD'};
$method =~ tr/A-Z/a-z/;
if ($method eq 'post') {
read(STDIN, $query, $ENV{'CONTENT_LENGTH'});
} else {
$query = $ENV{'QUERY_STRING'};
}
@assocarray = split(/&/, $query);
foreach $assoc (@assocarray) {
($property, $value) = split(/=/,
$assoc);
$value =~ tr/+/ /;
$value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C",
hex($1))/eg;
&jcode'convert(*value,
$charcode);
$form{$property} = $value;
}
}
わたしも、何やってんのか分かりません。みなさんも安心して下さい。
作者によれば、int_formというサブルーチンの働きは3つ。
1.QUERY_STRINGの内容を解析し、%formという連想配列に代入
簡単にいうと、ブラウザから送られた情報を整頓する、ってことです。
「連想配列」ってのは、ちょっと分かりかけてきた程度で言うのもずうずうしいですが、連想って英語のassociationあるいはassociatedぐらいで、「関連」って意味もあります。en.htmlの最初で出てくる、名前の入力を思い出して下さい。あそこで、NAME="yourname"と指定しましたよね。例えばあなたが、そこに「小渕恵三」って入れたとすると、yournameで「小渕恵三」を連想するようにする、2つに関連づけを行う、そのように処理した各項目を配列してゆく、ってことでしょう。ブラウザから送られてきた情報をperlが処理しやすい形に変えているって理解でいいと思います。
2.METHODが"GET"であっても"POST"であっても、その違いを意識しないですむ処理
これも「ブラウザから送られてきた情報をperlが処理しやすい形に変えている」っていう理解でいいと思います。
METHOD? GET? POST?って思った人は、en.htmlのとこ読み返して下さい。(苦労しているのはあなただけではないのです)
3.漢字コードを指定したものに変換。
# 初期化
&init_form(sjis);
ってとこ思い出して下さい。「変換?」って思った人は、え〜っと。わたしも最近まで知らなかったのでお仲間です。
WindowsもMacもシフトJISを使いますが、UNIXでは、eucってのも使えて(というよりeucが主流)それらを全部同じコードに統一してくれるっていうありがたい処理です。この処理してないと文字化けします。
よく見れば、下から3行目、
&jcode'convert(*value, $charcode);
ってとこで、今さっき外部から呼び出した、jcode.libを使ってその処理してんだろうなぐらいは分かります。
ってことで、私たちに必要な知識は:
# 漢字ライブラリの読み込み
require "jcode.pl";
で漢字コードを処理するライブラリを呼び出し、
# 初期化
&init_form(sjis);
を使って、自分が利用したいコードに統一する。
ブラウザから来た情報は「連想配列」たらいう%formに入っている。
あとは考えなくていい。うれしい。うれしい。うれしい。うれしい。
次
今回、アンケートはCSV形式で保存することにしました。
CSVとは、テキスト形式で、データごとをカンマで区切って保存します。例えば:
小渕恵三,連絡,作者から直に,これからはEメール魔になります
です。
まずen.csvというファイルを作ります。ファイル名は任意です。まだ中身はないので、名前だけのファイルです。
どう作るんだ? 答え:色々ありますが、Windowsならエクスプロアラーで、「ファイル」「新規作成」「テキスト文書」で「新規テキスト文書.txt」が出来ますからそれを名前変更。他にもテキストエディタで新規作成して、何にも入力せずに保存、とかできます。
あ、もちろん
名前,更新連絡,いかに知ったか,コメント
という内容のあるファイル作っても一向にかまいません。
さて、ファイルが出来たので開きましょう。
# データファイルのオープン
open(CSV, "+<en.csv");
ファイルをオープンするには、
open(ファイルハンドル名, "ファイル名");
の形式でします。ファイル名の前の+<は「読み書きモード」でオープンするという意味です。省略すると「読み込み」、>なら「書き込み」、>>なら「追加書き込み」だそうです。
ファイルハンドルは、文字通りファイルをハンドル(取り扱う)もの。ファイル専用の変数と思えばいいのでしょう。変数ってのは情報を入れる箱みたいなものと考えればいいそうです。
ということで、ファイルを「読み書きモード」で読み込み、その内容をCSVに入れました。
次
# ファイルのしっぽに行く
seek(CSV, 0, 2);
seekは探す。後ろの2つめの数字は、0 が文頭、1 が現在の位置(わたしこの意味分かってません)、2
が文末です。
最初の数字は、文頭・現在・文末で指定された場所から何バイト離れているか。負の数も指定できるそうです。
もとのプログラムは新しい情報をデータファイルの頭に追加していく形式になってましたkら、seek(CSV,0,0);
でした。別にそれでもかまわないけど、下に追加したいなあ・・。ここで「?」でした。たとえば「12345」というファイルの文末というと、5? でも、まだ書き込まれていない6の位置にいかなくちゃいけないからseek(CSV,
1, 2);って書くのかなあ? ま、いいややってみよ。0,2でうまく動いたので、それでいいんです。(オイオイ)
さて、ファイルも開いたし、位置決めもすんだし、さあ、書き込みです。
# データファイルの書き込み開始
# 名前
print CSV "$form{'yourname'},";
げ! どこから説明しよう?
print って「呪文」はまあ、「出力]です。
これわたしの山勘ですけど、コンピュータの初期ってディスプレイなんかなくて、パンチカードってんですか、穴あき紙でしょ。そんなんで、printっていうんやないだろか?と。
print "Hello World!";
とすると、Hello World と出力されます。今回は「データファイルに保存」ですから、
print CSV として出力先を特定します。
"$form{'yourname'},";
見ずらいのでおおきくしました。
ブラウザから来た情報は「連想配列」たらいう%formに入っている。
のでした。例えば
yourname=小渕恵三 sendem=sem howcome=作者から直に comment=これからはEメール魔になります。
みたいな形で入っているわけです。この場合、yournameで連想される「小渕恵三」が表示されることになります。
(実は、print CSV "$form"; とやってみたんですが何も出ない。)(知っている人から見たら馬鹿ってことしてるんだろうなあ・・・)
# 更新連絡
if ($form{'sendem'} eq 'sem') {
print CSV "連絡,";
} elsif ($form{'sendem'} eq 'noem') {
print CSV "不要,";
} else {
print CSV "不明,";
}
# ホームページをどう知ったか
print CSV "$form{'howcome'},";
# コメント
print CSV "$form{'comment'},";
さて、ここで ””と’’の違いです。””で囲むと、変数の中身が、この場合なら、小渕恵三,が出力されます。’’で囲むと、まんま出力されます。つまり
$name, と出力されます。
$name, の最後のコンマは何なんだ? CSVはデータをコンマで区切る形式でした。ということで、ここに、ちょこっとコンマを置いとけばいいのです。
次
# 更新連絡
if ($sendem eq 'sem') {
print CSV "連絡,";
} elsif ($sendem eq 'noem') {
print CSV "不要,";
} else {
print CSV "不明,";
}
これ別にこうしなくちゃいけないというとこはありません。小渕恵三さんの時のように
print CSV "$sendem,";
としてもいいです。その場合、送られてきたデータに従って、sem、noem、データなし(何も表示されない)、になります。ここでは漢字の方がいいよなあ、ということで「連絡」「不要」「不明」に置き換えることにします。あ、この部分最初は、「連絡」と「不要」だけでした。そのときのプログラムは、
if ($sendem eq 'sem') {
print CSV "連絡,";
} else {
print CSV "不要,";
}
でした。
if は「もし」,eq は「イコール」のことです。else「他の場合は/そうでなければ」です。半角で見ずらいですが、{ と } です。ここにやりたい処理を書きます。
それでしみじみ見てもらえれば理解できますよね。理解したあとで、次を見て貰えば、これも大丈夫ですよね。elsif はelse if をくっつけたものでperl独特らしくて、C言語経験者が間違いやすいんだそうです。
if ($sendem eq 'sem') {
print CSV "連絡,";
} elsif ($sendem eq 'noem') {
print CSV "不要,";
} else {
print CSV "不明,";
}
さて、次
# ホームページをどう知ったか
print CSV "$howcome,";
# コメント
print CSV "$comment,";
問題なし。次
# 記入日時
$datestr = &get_date_string;
print CSV "$datestr\n";
「ん?」$datestr という変数に、サブルーチン &get_date_string から得られるデータを代入している。そしてそれを、CSV に出力している。ということは分かったでしょうか?そこまで分かれば、あなたはもう、「さっぱりわからない」状態から抜け出て、ちょっと光がさしてきたと・・・・。
&get_date_stringは後ろにあります。これです。
# 現在日時を文字列化する
# Copyright (C) 1997 by Hiroshi Yuki.
sub get_date_string {
local($sec, $min, $hour, $day, $mon, $year);
( $sec, $min, $hour, $day, $mon, $year ) = localtime(time);
$year += 1900;
$mon++;
# 必要なら0を付加する
if ($hour < 10) {
$hour = "0$hour";
}
if ($min < 10) {
$min = "0$min";
}
if ($sec < 10) {
$sec = "0$sec";
}
return "$year/$mon/$day/$hour:$min:$sec";
}
オリジナルをちょっと変えてます。といっても最後の行。オリジナルは
return "$year年$mon月$day日$hour時$min分$sec秒";
になってたってだけです。
このサブルーチンは頑張って解読しました。が説明するのは疲れるのでしません。気になる方は「とほほのWWW入門」のperlの説明書をプリントアウトして、そうですねえ、わたしは1時間ほどかかかったと思いますが、あちこちめくってメモでもしながら「推理」してみて下さい。(解読出来たときはたのしいよ。5行目の、$mon++; の秘密とか。)
ま、ここでは:
# 記入日時
$datestr = &get_date_string;
print CSV "$datestr";
とすると、1999/7/7/12:29:13 のように出力される、って分かっておけば十分。
便利な部品です。
あ、忘れずに。これが最後のデータですので、\n をつけます。これは改行してくれ、っていう印です。これ忘れてもperlは正常に動きますが、データが、前の人後の人次の人・・・と切れ目なしで続きます。
次
# 以上で新規データの書き込み終了
# データファイルのクローズ
close(CSV);
ファイルハンドルをクローズするだけで、これまでメモリ上にあるデータをハードディスクに書き込んでくれるみたいです。簡潔。感動的ですらある。
ごくろ〜さま、ということで書き込み完了を書き込んだ人に伝えましょう。
その前に、前の方で
# ヘッダの送出
print "Content-type: text/html\n\n";
ってありましたよね。これにすぐ続けて読んだ方がわかりやすいんですが:
# 書き込み成功をブラウザに伝える。
print "<HTML>\n";
print "<HEAD>\n";
print "<TITLE>書き込み完了</TITLE>\n";
print "</HEAD>\n";
print "<BODY BGCOLOR=white>\n";
print "<H1>書き込み完了</H1>\n";
print "ありがとう!\n";
print "<HR>\n";
print "</BODY>\n";
print "</HTML>\n";
# ヘッダの送出
print "Content-type: text/html\n\n";
print の「作法」は後で説明しますが、Content-type: text/html つまり「中身はテキスト形式でhtmlだよ」って教えないと、ブラウザは普通のテキスト扱いします。つまり、<hr>とか書いても、
とhr=水平線を表示せずに、ただ
<hr>
と表示します。それは困ります。
これは、ブラウザに渡すHTMLファイル(ファイルと呼べるんだろうか?)の部分です。
ブラウザには:
Content-type: text/html
<HTML>
<HEAD>
<TITLE>書き込み完了</TITLE>
</HEAD>
<BODY BGCOLOR=white>
<H1>書き込み完了</H1>
ありがとう!
<HR>
</BODY>
</HTML>
とわたります。
HTMLを知っている人には簡単な内容なので説明しません。分からない人は「講座」をどうぞ。
蛇足ですが、この部分はブラウザではen.cgiとなります。プログラムとしてのcgiはテキストファイルですが、ブラウザが受け取ったcgiという拡張子をもつファイルの実体は、ある時はhtml、ある時はgif、はたまたある時はjpegであります。
すんだすんだ。
で、
# 終了
exit(0);
ごくろ〜さまでした〜。
さて、一応一通り見てきましたが、いかかだったでしょうか?思ったより簡単?複雑?
でも、実際使っているプログラムはもうちっと複雑です。さあ、次は実物だ!
でも解説は、上の解説で触れなかったとこだけです。もう自分でプログラムを読む基礎は身に付いたはず?
(意味が理解できると感動する部分があります。少なくともわたしは感動しました)
#!/usr/local/bin/perl
# # en.cgi # # Ver 1.0 # # This cgi.file is based on # guestadd.cgi Version 1.0.5 # Copyright (C) 1997 by Hiroshi Yuki. # All rights reserved. # 結城浩 <hyuki@st.rim.or.jp> # http://www.st.rim.or.jp/~hyuki/ # ########## # ↓保存されるアンケートのファイル名 $csvfile = 'en.csv'; # ロック関連
# 漢字ライブラリの読み込み
# 初期化
# ヘッダの送出
$name = $form{'yourname'};
# 名前が入力されているかどうかの確認
# 更新希望なのに、メールアドレスが入力されていない
# タグを置換する
# データファイルのオープン
# データファイルのロック
# ファイルのしっぽに行く
# データファイルの書き込み開始 # 名前
# メール
# 更新連絡
# 性別
# 年齢
# ホームページをどう知ったか
# ホームページ
# コメント
# 記入日時
# 以上で新規データの書き込み終了 # データファイルのアンロック
# データファイルのクローズ
# 書き込み成功
# 終了
#
# エラー表示
# タイトル部分
# ページの終わり
# 現在日時を文字列化する
# フォームからの情報を連想配列 %form に入れる
# ロック
# アンロック
|
ってのは次回やりますが、のせとくだけのせときますね。しみじみながめて何やっているのか考えてみて下さい。
じゃあね!