[perl] 学習メモ:Hello world!! → GET値の取得と処理 → ファイルの内容出力

ちょっとPerlが必要になったので、Hellow worldからの過程をメモることにします。
このエントリーの最終目標は、[Ajax + PHP] Web2.0的にHTMLソースを隠す方法 で晒した Request.phpでやってることで、

  1. GETを取得(pとaction)
  2. 取得したGET値pに基づいてファイルを読み込む
  3. 読み込んだファイルの内容を出力

です。
Perlが使えるとさくらインターネットのライトプランみたいなPerlのCGIしか許可されてない鯖で強い。
安価な鯖だとPHPダメって所は多いです。

現時点での自分の状況を書くと…

  • CGI設置経験は豊富(でも最近ご無沙汰)
  • PHPやJavaScriptの文法を元にそこそこ読めるけどイチからは書けない
  • 人が書いたソースのカスタマイズはそれなりに出来る
  • Perlでのファイル操作は全く知識がない
  • POSTとGETの扱いも良く分からない
  • C++に挫折経験あり

設置は経験あるけど作ったことはないっていう状況。
自分の中で消化する為にPHPと比較しながら覚える手を使ってます。
ZeroMail作り始めた時もこんなんでしたわ。

PerlはDreamweaverでサポートされてないので、テキストエディタMeryを使用した。
Exchangeに何かあるかと思ったけどさっぱりだった。

PerlでHello world!!

ローカル環境はXAMPPです。パスはxamppデフォルトそのまんまで、文字コードはUTF-8。

#!/xampp/perl/bin/perl

print "Content-type: text/html; charset=UTF-8\n\n";
print "Hello world!!\n";

拡張子cgiのファイル作って上のソースコピペしてブラウザで開けばHello worldが出力されるのだが、
content-typeが無いとmalformed header from script. Bad header云々のエラーが出る。
文字コードの扱いに関してはPHPよりも厳しいんだよな、確か。

GET値の取得

PHPとかーなーり勝手が違う。

#!/xampp/perl/bin/perl

print "Content-type: text/html; charset=UTF-8\n\n";

if($ENV{'REQUEST_METHOD'} eq "GET"){

print $ENV{'QUERY_STRING'};

}

$ENVは環境変数。 PHPでも$_ENVって書く。

比較演算子

まずif文のeqはequalの頭2文字。
PHPとかJavaScriptだと文字列だろうが数値だろうが==でいけるけど、
Perlの場合は記号の比較演算子が有効なのは数値比較だけで、
文字列比較の場合には文字列の比較演算子を使わねばならんらしい。
そういうわけなので、リクエストメソッドがGETだったらtrueという意味になる。

QUERY_STRING

何となく聞き覚えのある単語。直訳でクエリ文字列。
GETの場合にはURLのハテナマークの後ろに続く文字列のことですね分かります。
PHPだと$_SERVER['QUERY_STRING']だから処理も同じになる…とすれば、
&で分解してイコールの左がキーで右が値という具合になりますか。
PHPの$_GET[“hoge”]って感じでキーを指名する値の得方はライブラリ使わないと無理らしい。

とりあえずGET値を&で分割

if($ENV{'REQUEST_METHOD'} eq "GET"){

	@array = split(/&/,$ENV{'QUERY_STRING'});

	local ($, , $\) = ('<br /> ', "\n");
	print "Content-type: text/html; charset=UTF-8\n\n";
	print @array;

}

Perlでの配列

配列には@が付く。見慣れればパッと見で「ああ配列か」と思えてくるのかなこれは。
まあそれはそれとしてsplitで文字列分解したのを配列に格納する辺りは分かりやすい。
ただこれそのままprintしても配列に格納されてるものが全て出力されないので、
ここを参考に7行目のlocalから始まる行でver_dumpっぽくしている。
既にこの文字列からして意味不明にも程があるのだが、ver_dumpだと思って今回はスルーしておく。

今のところ@arrayには単純なGETされた値が横並びで入るのみ。まだイコールがそのまま残っている。

イコールでさらに分割

配列の処理と言ったらforeachですよforeach。

#!/xampp/perl/bin/perl

if($ENV{'REQUEST_METHOD'} eq "GET"){

	@array = split(/&/,$ENV{'QUERY_STRING'});

	print "Content-type: text/html; charset=UTF-8\n\n";

	foreach $set (@array) {
		($key,$value) = split(/=/, $set);
		print "$key => $value <br />";
	}

}

さすがPerlはforeachの書き方も一味違った。

Perl の foreach文

結論から言うと、配列の各値が代入される変数がforeachとカッコの間にくる

PHPと前後が逆ってことですね。

 foreach ($array as $set)

連想配列でキーと値欲しい時は=>使うのかな。でもあったら書いてあるよな…。まあ今は使わないのでスルーしておく。
イコールでの分解はsplit。キーと値を格納する変数はカッコに入れてまとめて定義する。

送信された値のチェック

これでGET送信されたキーと値が得られるけれども、POSTやGET送信で何かしら送られてきたら必ず値のチェックをしなければならない。
最低限除去しておくべきはHTMLタグ…。strip_tag的なものはあるのかしらと探してみたら、さすがperlは(ry

参考:にししファクトリー - HTMLタグを取り除く

$source =~ s/<.*?>//g; 

なにこれwwwwwwwwwwwwwww
Perlは簡略化を競う言語らしいけど、まさにその典型みたいな…。
正規表現に関する関数が無いってことに驚いた。

PHPだとpreg_replaceになる。

$source = preg_replace("/<.*?>/i", "", $source);

Perlでの文字列置換構文:
返される値 =~ s/パターン文字列/置換文字列/修飾子;

先頭にsって着いてスラッシュが多ければ置換。スラッシュ2本しかなければマッチ。
値が半角英数しか来ないのならそのような正規表現にしてしまった方が楽かも。

#!/xampp/perl/bin/perl

if($ENV{'REQUEST_METHOD'} eq "GET"){

	@array = split(/&/,$ENV{'QUERY_STRING'});

	print "Content-type: text/html; charset=UTF-8\n\n";

	foreach $set (@array) {
		($key,$value) = split(/=/, $set);

		$value =~ s/%(\w+)|\W//eg;#URLエンティティ文字と記号置換

		print "$key => $value <br />";

	}

}

こんな感じで。

連想配列に値を保存

$keyと$valueに分割出来たわけだが、$keyが増える度に変数が増えるというのは非効率的なので連想配列にする。
理想とする配列構造はこんな感じ。

array(
	"action" => "actionvalue",
	"page" =>"pagevalue"
)

PHPの場合だと変数と大カッコで連想配列が作れてしまうんだけど、

$array["action"]="actionKey";

Perlの場合は接頭の記号が変わるし、使うカッコの種類まで変わる。

%array = (); #定義と初期化
$array{"action"} = "actionKey";

変数は$で配列は@で連想配列は%。
配列は[大カッコ]で連想配列は{おっぱいカッコ} ですって。
配列で大カッコの場合に指定出来るキーは数字だけ。
文字列のキーにするなら%とおっぱいカッコだ!( ゚∀゚)o彡゜おっぱい!おっぱい!

上記を踏まえて、printの行とかを連想配列に変更する。

#!/xampp/perl/bin/perl

if($ENV{'REQUEST_METHOD'} eq "GET"){

	@array = split(/&/,$ENV{'QUERY_STRING'});
	%get = ();

	print "Content-type: text/html; charset=UTF-8\n\n";

	foreach $set (@array) {
		($key,$value) = split(/=/, $set);

		$value =~ s/%(\w+)|\W//eg;#URLエンティティ文字と記号置換

		$get{$key} = $value;
		#print "$key => $value <br />";

	}

}

getという連想配列にGETされたkeyとvalueを入れました。
perlのコメントは#ですよ奥さん。

GETメソッドで渡されたactionにはviewという値が、pageにはファイル名が入っているとします。
それらはgetという連想配列から取得出来るようになってるので、ファイル名を作る条件文は次のようになる。

if($get{"action"} eq "view" && $get{"page"} ){
    $name = "pages/".$get{"page"}.".html";
}

ファイル名が出来たらファイルをオープンして出力!
ファイル関数で開いて出力して閉じるっていうプログラムの流れはPHP等とほぼ同じ。

if(open FH , $name ){
	while ( $line = <FH> ) {//
		print $line;
	}
}else{
	print "error:404";
}

open関数の構文

open ファイルハンドル, "ファイル名"

ファイル読み込みの構文

配列 = <ファイルハンドル>

close命令の構文
close ファイルハンドル

ファイルハンドルは全部大文字で書くのが常識らしい。

渡されたファイル名と違うファイルを呼び出す場合にはSwitchを使うのがスマートだと思う。
PHPと同じ調子で書いたらエラーが出たもんだから調べたら、
PerlでSwitchを使う場合はSwitchモジュールを呼び出す必要があるんだって!

#!/xampp/perl/bin/perl
use Switch;

//省略//

if($get{"action"} eq "view"){

	switch($get{"page"}){
		case "cat" {
			$name = "pages/nyan.html";
		}
		case "dog" {
			$name = "pages/wan.html";
		}
		else {
			$name = "pages/".$get{"page"}.".html";
		}
	}
}

いずれのcaseにも当てはまらない場合はdefaultじゃなくてelse
そしてコロンとbrakeじゃなくて括弧だった。

ソース全文

#!/xampp/perl/bin/perl
use Switch;

if($ENV{'REQUEST_METHOD'} eq "GET"){

	print "Content-type: text/html; charset=UTF-8\n\n";#文字エンコード

	@array = split(/&/,$ENV{'QUERY_STRING'});#&で分割
	%get = ();

	foreach $set (@array) {
		($key,$value) = split(/=/, $set);#=で分割

		$value =~ s/%(\w+)|\W//eg;#URLエンティティ文字と記号置換

		$get{$key} = $value;
		
	}

	if($get{"action"} eq "view" && $get{"page"} ){
		$name = "pages/".$get{"page"}.".html";
	}

	if(open FH , $name ){
			while ( $line = <FH> ) {
				print $line;
			}
			close $name;
	}else{
		print "error:404";
	}
}

参考資料

Leave a Comment.