Perl で,実行時に動的にモジュールを呼ぶときには頻繁に require が使われたりしますが,今日それをしようとしたら見事に require の罠にハマってしまいました.
perldoc -f require をしてもらえれば分かるのですが,
require Foo::Bar;
と
require "Foo::Bar";
には違いがあります.前者は @INC に格納された全てのディレクトリを起点として Foo/Bar.pm を探しに行きます.つまり,'::' はファイルセパレータに変換され,拡張子 '.pm' が自動的に付加されます.
一方で後者は,@INC に格納された全てのディレクトリを起点とするところまでは一緒ですが,"Foo::Bar" ファイルを探しに行きます.
この動作を理解するために,次のような実験ができます.
$ cat Foo/Bar.pm package Foo::Bar; 1; $ perl -e 'require Foo::Bar' $ echo $? 0 # 正常終了 $ perl -e 'require "Foo::Bar"' Can't locate Foo::Bar in @INC (@INC contains: /opt/local/lib/perl5/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/5.10.0 /opt/local/lib/perl5/site_perl/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/site_perl/5.10.0 /opt/local/lib/perl5/vendor_perl/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/vendor_perl/5.10.0 /opt/local/lib/perl5/vendor_perl .) at -e line 1. $ echo $? 2 # 異常終了してる!
Foo/Bar.pm ファイルは空のモジュールファイルですが,"require Foo::Bar" を実行するとそのモジュールファイルを読み込む require が正常に終了していることが分かります.逆に "require "Foo::Bar"" は見事に失敗しています.
require $module
では,以下のようにした場合はどうなるでしょうか.
my $module = "Foo::Bar"; require $module;
実際にやってみると分かりますが,これは "Foo::Bar" ファイルを探しに行ってしまいます.
$ perl -e '$module="Foo::Bar"; require $module' Can't locate Foo::Bar in @INC (@INC contains: /opt/local/lib/perl5/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/5.10.0 /opt/local/lib/perl5/site_perl/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/site_perl/5.10.0 /opt/local/lib/perl5/vendor_perl/5.10.0/darwin-thread-multi-2level /opt/local/lib/perl5/vendor_perl/5.10.0 /opt/local/lib/perl5/vendor_perl .) at -e line 1.
実行時までどのモジュールを読み込むかを決定できないときに,変数の中にモジュール名を入れて require するというのは良く使われるテクニックです.それがこんな風にファイルセパレータに変換されないわ,拡張子は付加されないわでは困ってしまいます.
解決策
要は require の引数が bareward であれば良いわけで,eval を使えば解決されます.
my $module = "Foo::Bar"; eval "require $module";
require が eval によって実行されるとき,$module は既に "Foo::Bar" に展開されているので,require からは引数が Foo::Bar という bareword にしか見えません (見えないというか,実際に bareword になっている).
というわけで,下記の通り正常に実行されるというわけですね.
$ perl -e '$module="Foo::Bar"; eval "require $module"' $ echo $? 0
perldoc -f require に全部書いてあることだけど.