eval を使う

作成日:2001-01-20
最終更新日 :

さて、更新すべきファイルを事前に調べてからはじめて ftp のコネクションを張る、 という方法を実装してみた。

調べた結果をどこに保存しておこうか、結果のフォーマットはどうしようかと少し悩んだ。 その結果、調べた結果は perl のプログラムの形で、変数として保存するということにした。 たとえば、私のホームページのトップから、ディレクトリ faure の下にあるファイルいくつかを更新すべき時は、 $prog という変数にこんな文字列が入っている。


chdir afisxejo || die "Can't cd to :/afisxejo";
chdir '::';
$ftp->cwd("..");
chdir faure || die "Can't cd to :/faure";
$ftp->put("index.html.ja");
$ftp->put("kvinteto2.html.ja");
$ftp->put("kvinteto210.jpg");
$ftp->put("kvinteto210.mid");
$ftp->put("libroj.html.ja");
chdir '::';
$ftp->cwd("..");
chdir js || die "Can't cd to :/js";
chdir '::';
$ftp->cwd("..");
# 以下続く

このとき同時に無駄な命令のカットができることは前回も述べた。 しかし、せいては事をしそんずる、とも言う。 不要な命令のカットは、今回はあきらめることにした。 後のリファクタリングにとっておこう。

擬似コードにすると次の通り。サブルーチンの戻り値を使ったプログラムである。


$prog = &dodir(':');
eval($prog);
# ...
sub dodir{
  for (@filenames) {
    if ((-f $_) && (-M $_ < $kankaku)) {	# 前回更新以後作られたファイルなら
      $prog .= "\$ftp->put(\"$_\");\n";
    }
	# $_ はディレクトリなので再帰的に処理する
	# chdir $_ || die "Can't cd to $name";
	$prog .= "chdir $_ || die \"Can\'t cd to $name\";\n";
	# $ftp->cwd($_); 
	$prog .= "\$ftp->cwd($_) || die \"Can\'t cd to $name\";\n";		# !!! 
		$prog .= &dodir($name,$nlink);
		# chdir '::';
		$prog .= "chdir '::';\n";
		$prog .= "\$ftp->cwd\(\"..\"\);\n";
	}
	$prog;
}

このように書いた結果、呼び出し元の変数 $prog にはそれらしい文字列が入っていた。 いざ、eval しようと思ってテストしてみたが、何も転送されない。 3 分間考えてわかった。eval する前の準備を忘れていた。 Net::FTP モジュールを呼び出したり、 $ftp オブジェクトの初期化をしたりすることも、 呼び出し元でなく、eval される文字列で行なわなければならない。 こういうときには、ヒアドキュメントという方法があったな、 ということまでは思い出したが、 今日は時間切れだ。

翌日、見直してみた。 なんと、上のコードの # !!! にあたる行を書くのを忘れていた。 この行に対応するコードを書いて、 わざわざヒアドキュメントを書くこともないことがわかった。 ということで、上で書いたまちがったことは上書き線を引いておいた。 なぜまちがいがわかったかというと、「Perl の国へようこそ」という本に、 eval についての記述があったからだ。

それでは、なぜ # !!! にあたる行を書くのを忘れていたかを説明する。 実は忘れていたのではなく、コメントアウトしたままにしておいたのだった。 コメントアウトは次のようにしていた。


	# if (!($ftp->cwd("$_")) ) {	# 指定されたディレクトリにいけなければ
	#	$ftp->mkdir("$_");			# ディレクトリを作り
	# 	$ftp->cwd("$_") || die Can't goto remote dir $name; # そのディレクトリに行け。さもなくば死ね
	# }

この 4 行全体が今回の目的にはいらないと思い込んでいた。 まずは 1 行目の個所が必要だったにもかかわらず、 目に入っていなかった。 こういうことがあるから、一つの行で複数の作業を行なうことはこわい。 解決策としては二つある。一つは冗長に書くことだ。


	$status = $ftp->cwd("$_");
	if (!$status) {
		...
	}

もう一つは、上の文全体を簡潔にしてしまうことだ。たとえばこんな感じか。

$ftp->cwd("$_"), $ftp->mkdir("$_")unless $ftp->cwd("$_") || die "Can't go to remote dir $name";

第3の書き方もあるかもしれない。

$ftp->cwd("$_") || ($ftp->mkdir("$_") && $ftp->cwd("$_") ) || die "Can't go to remote dir $name";

少し考えて、最後の第3の書き方にした。

なお、書き落としたが、Mac の場合は、 頼みもしないのに Iconという名前で始まるファイルを転送しようとして 「ファイルが開きません」と文句を言われる。 そういうときはさっさと飛ばす。

後記:この問題に関してはわざわざ eval を使う必要はないと思っているが、 具体的にどうすべきかはわからない。今は Mac を使うのをやめ、Windows にしたこと、 そして Windows では Perl ではなく Ruby と ftpup を使っていて、 間に合っているからだ。

まりんきょ学問所perl手習いの部屋 > eval を使う


MARUYAMA Satosi