さて、更新すべきファイルを事前に調べてからはじめて 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 を使う