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 を使う必要はないと思っているが、 具体的にどうすべきかはわからない。今は Ruby と ftpup を使っていて、 間に合っているからだ。
まりんきょ学問所 > perl手習いの部屋 > eval を使う