Perl サブルーチン集


Perl でよく使う処理のサブルーチンを紹介します。

サブルーチン集 / Query / 排他処理 / メール送信 / ホームページ取得 / 戻る / トップページ


Perl サブルーチン集

私が Perl でよく使う処理をサブルーチンの形で紹介します。 ご自分で実装をする時に参考にでもしてください。 Perl のバージョンは 4 でも良いようになっています。
 
なお、以下のサブルーチンは jcode.pl の使用を前提としています。

Query 取得

CGI に渡されたデータを読み取る処理です。 POST 型、GET 型いずれにも使え、 デバッグ時のコマンドプロンプトの引数にも使えます。 タグ入力の無効化、半角カナ→全角カナの変換、 漢字コードの Shift-JIS への変換も行います。
sub get_query
{
	local(@pairs,$pair,$key,$value,$data);
	
	if ($ENV{"REQUEST_METHOD"} eq "POST")
	{
		read(STDIN,$data,$ENV{"CONTENT_LENGTH"});
	}
	else
	{
		$data=$ENV{"QUERY_STRING"};
	}
	
	@pairs=(split(/&/,$data),split(/&/,join("&",@ARGV)));
	%query=();
	foreach $pair (@pairs)
	{
		($key,$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;
		
		if ($value eq ()) { $value=""; }
		
		&jcode'h2z_sjis(*value);
		&jcode'convert(*value,"sjis");
		
		$query{$key}=$value;
	}
	
	%query;
}
このサブルーチンの返り値は連想配列として返ってきます。 次のように使います。
%query=&get_query();
"key1=value1&key2=value2&..." のような形で CGI にデータを渡すと (フォームを使うと普通この形になります)、 $query{"key1"} で value1 が取れます。
 
また、"key3=value3&key4&..." のような形で CGI にデータが渡された時に、 defined($query{"key3"}) で "key3" が含まれているかどうかチェックできます。
 
この処理では半角カナと同じコードを含んだ日本語 EUC が送られてきた場合、 思わぬ文字化けを起こす可能性があります (SJIS 専用の半角カナ→全角カナ変換を文字コードが特定される前に 使っており、本来はやってはいけない方法を使用しているため)。 それを防ぐためには &jcode'h2z_sjis(*value); の行を削除して 半角カナ→全角カナの変換を無効化してください。

排他処理

CGI でファイルをアクセスする場合、 その CGI のページが複数の人に同時に見られた時に、 ファイルを正しく処理できるようにする必要があります。 そのためにファイルを排他処理する方法です。 Java の synchronized に相当します。
sub lock
{
	local($dev ,$ino ,$mode ,$nlink,$uid  ,$gid);
	local($rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
	local($lockfile);
	
	$lockfile=$_[0].".lock";
	while (-e $lockfile)
	{
		($dev ,$ino ,$mode ,$nlink,$uid  ,$gid,
		 $rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)=stat($lockfile);
		if (time()-$ctime>$_[1]) { last; }
		sleep(1);
	}
	open(LOCK,">".$lockfile);
	close(LOCK);
}

sub unlock
{
	unlink $_[0].".lock";
}
ファイルにアクセスする前に次のようにファイルをロックします。
&lock(ロックするファイル名,最長ロック時間);
ロック直後にプログラムが異常終了することも考えられるため、 最長ロック時間 (秒数) を設定し、 それよりも長い時間ファイルがロックされないようにします。
 
ファイルへのアクセスが終わったらファイルをアンロックします。
&unlock(ロックしたファイル名);
ファイルのロックには「ファイル名.lock」というサイズ 0 の ファイルを作ったり消したりしているので、 そのディレクトリのパーミッションを書きこみ可能に設定する必要があります。

メール送信

メールを送信する方法です。
$sendmail="/usr/lib/sendmail";
$system_mailer="mailer";

sub sendmail
{
	local($from,$to,$subject,$body);
	
	($from,$to,$subject,$body)=@_;
	
	open(MAIL,"|".$sendmail." -t");
	
	print MAIL "X-Mailer: ".$system_mailer."\n";
	print MAIL "Errors-To: ".$to."\n";
	print MAIL "To: ".$to."\n";
	print MAIL "From: ".$from."\n";
	print MAIL "Subject: ".(&jcode'jis($subject))."\n";
	print MAIL "Content-Transfer-Encoding: 7bit\n";
	print MAIL "Content-Type: text/plain\; charset=\"ISO-2022-JP\"\n";
	print MAIL "\n";
	print MAIL $body;
	
	close(MAIL);
}
$sendmail には sendmail プログラムのパスを、 $system_mailer にはメールを送信するプログラム自体の名前 (本当はメーラーの名前を書くのですが、 プログラム中から送るのであればその名前を書くのがエチケットだと思います) を指定しておきます。
&sendmail(送信元,送信先,タイトル,本文);
このようにして送ります。 万が一、プログラムにバグがあると ネットワークの利用者や管理者に迷惑をかけてしまいますので、 前もって入念にデバッグはしておきましょう。

ホームページ取得

ホームページを読み取る方法です。
$system_agent="agent";

sub get_homepage
{
	local(%homepage,$address,$host,$path,$port);
	
	$address=$_[0];
	
	%homepage=();
	
	if ($address=~/^(http\:\/\/)([\.\-\:_0-9a-z]+)(\/[\/~&+%=\.\-\:_0-9a-z]*)$/g)
	{
		$host=$2;
		$path=$3;
	}
	else
	{
		$homepage{"error"}="不正なURLです。";
		
		return %homepage;
	}
	
	if ($host=~/^(.*)\:(\d*)$/g)
	{
		$host=$1;
		$port=$2;
	}
	else
	{
		$port=80;
	}
	
	$addr=(gethostbyname($host))[4];
	$name=pack("S n a4 x8",2,$port,$addr);
	socket(SOCKET,2,1,0);					# or 2,2,0
	if (!connect(SOCKET,$name))
	{
		$homepage{"error"}="接続に失敗しました。";
		
		return %homepage;
	}
	binmode(SOCKET);
	select(SOCKET);$|=1;select(stdout);
	
	print SOCKET "GET ".$path." HTTP/1.0\n";
	print SOCKET "Connection: close\n";
	print SOCKET "Host: ".$host."\n";
	print SOCKET "User-Agent: ".$system_agent."\n";
	print SOCKET "Referer: ".$this_url."\n";
	print SOCKET "Accept: text/html\n";
	print SOCKET "\n";
	
	$header=1;
	while ($line=<SOCKET>)
	{
		if ($header)
		{
			chop $line;
			chop $line;
			
			if (length($line)==0)
			{
				$header=0;
			}
			elsif ($line=~/^HTTP\/([\d\.]+) (\d+) (.+)$/g)
			{
				if ($2 ne "200")
				{
					$homepage{"error"}=$2." ".$3;
				}
			}
			elsif ($line=~/^(.+)\: (.+)$/g)
			{
				$homepage{$1}=$2;
			}
			else
			{
				print $line;
			}
		}
		else
		{
			$homepage{"body"}=$homepage{"body"}.$line;
		}
	}
	
	close(SOCKET);
	
	%homepage;
}
$system_agent にはホームページを取得するプログラム自体の名前 (本当はブラウザの名前を書くのですが、 プログラム中に取得するのであればその名前を書くのがエチケットだと思います) を指定しておきます。 また、socket(SOCKET,2,1,0); の行は Windows の場合です。 Unix の場合は socket(SOCKET,2,2,0); と書き換えてください。
%homepage=&get_homepage(取得したいURLアドレス);
このようにしてホームページを取得します。 エラーがあった場合は $homepage{"error"} にエラーメッセージが含まれます。 ホームページの中身は $homepage{"body"} に、 ヘッダ情報は $homepage{ヘッダ名} に入ります。 ヘッダ情報は例えば、"Last-Modified" には最終更新日が入ります。
 
万が一、プログラムにバグがあると ネットワークの利用者や管理者に迷惑をかけてしまいますので、 前もって入念にデバッグはしておきましょう。 また、ホームページ管理者の著作権にも気をつけてください。

戻る