.pm 散策

Perl の標準モジュールは、有用なツールとノウハウの宝庫です。しかし、あまり解説した本がないようだし、ひとつ散策してみようと思います。なかには、ネットワークの知識やデータベースの知識など予備知識にかなりのものが要求されるものもあるので、できるだけ、予備知識のいらないものを選びたいと思います。

まず、wlocate .pm として(さりげなく宣伝) Perl モジュールのリストを作ります。このリストを基に .pm ファイルをブラウズしていったら、Env.pm と言うのがありました。Env.pm のはじめの部分は次のようになります。

package Env;

=head1 NAME

Env - perl module that imports environment variables

=head1 SYNOPSIS

    use Env;
    use Env qw(PATH HOME TERM);

=head1 DESCRIPTION

Perl maintains environment variables in a pseudo-hash named %ENV.  For
when this access method is inconvenient, the Perl module C allows
environment variables to be treated as simple variables.

どうもはじめの方はドキュメンテーションのようです。Perl モジュールではコードと文書がひとつのファイルに一体化しているので、文書の内容とコードの機能が違うということがないので便利です。Perl モジュールの文書だけを読む方法は別にあるので、ファイルの後の方にあると思われるコードを探します。Env.pm のコードは次のような短いものでした。

sub import {
    my ($callpack) = caller(0);
    my $pack = shift;
    my @vars = @_ ? @_ : keys(%ENV);
    return unless @vars;

    eval "package $callpack; use vars qw("
         . join(' ', map { '$'.$_ } @vars) . ")";
    die $@ if $@;
    foreach (@vars) {
        tie ${"${callpack}::$_"}, Env, $_ if /^[A-Za-z_]\w*$/;
    }
}

sub TIESCALAR {
    bless \($_[1]);
}

sub FETCH {
    my ($self) = @_;
    $ENV{$$self};
}

しかしながら、コードをみても何のことかさっぱり分かりません。そこで Env.pm の文書を表示させてみます。これには、perldoc というコマンドを使います。別のウィンドウでコンソールを立ち上げた後、次のように入力します。

$ perldoc Env

すると次のような文章が表示されます。man ページと同じでページャは less が使われているようです。less の操作を知っていれば自由に使えます。分からないときは h を入力すると、キーバインディングの説明がでます。

Env(3)         User Contributed Perl Documentation         Env(3)


NAME
       Env - perl module that imports environment variables

SYNOPSIS
           use Env;
           use Env qw(PATH HOME TERM);


DESCRIPTION
       Perl maintains environment variables in a pseudo-hash
       named %ENV.  For when this access method is inconvenient,
       the Perl module Env allows environment variables to be
       treated as simple variables.

       The Env::import() function ties environment variables with
       suitable names to global Perl variables with the same
       names.  By default it does so with all existing

英語なのが辛いところですがどうやら Perl で環境変数は %Env{ 'PATH' } 等のようにハッシュになっているのを @path = split(/:/, $PATH); のように普通の変数として扱えるようにするためのモジュールのようです。そこで次のようなファイル test_env.pl を作成してテストしてみます。

#!/usr/bin/perl
use Env;
print "$LANG\n";

実行結果は次のようになります。

$ perl test_env.pl 
ja_JP.ujis

使い方が分かったところでコードに戻ります。先ず import メソッドを覗いてみます。

sub import {
    my ($callpack) = caller(0);
    my $pack = shift;
    my @vars = @_ ? @_ : keys(%ENV);
    return unless @vars;

    eval "package $callpack; use vars qw("
         . join(' ', map { '$'.$_ } @vars) . ")";
    die $@ if $@;
    foreach (@vars) {
        tie ${"${callpack}::$_"}, Env, $_ if /^[A-Za-z_]\w*$/;
    }
}

結構短いのでほっとしますが、良く見るとさっぱり分からないのでガックリします。なにしろ先頭の caller(0) と言う関数が全く何をするものか分かりません。

そこで、perldoc を表示していたウィンドウに戻って q で perldoc を終了させた後次のように入力して、Perl の関数の解説をした perlfunc のマニュアルページを表示します。

$ man perlfunc

ここで、caller 関数の説明を読みたいのですが、ただ単に /caller と入力しただけではいろんな場所の caller が検索されてしまって目的の場所になかなか到達できません。

/^       caller

と入力します。^ と caller の間にスペースを7ついれるのがコツです。caller がページの先頭にきて次のような画面になります。

       caller EXPR

       caller  Returns the context of the current subroutine
               call.  In a scalar context, returns the caller's
               package name if there is a caller, that is, if
               we're in a subroutine or eval() or require(), and
               the undefined value otherwise.  In a list context,
               returns

                   ($package, $filename, $line) = caller;

またまた英語ですが、どうやら、あるパッケージから caller を含むサブルーチンを呼び出すと、caller の戻り値がもとのパッケージのパッケージ名になるようです。そこで caller をテストするためのスクリプト test_caller.pl を次のように作成します。

package CALL;
sub test {
    my($callpack) = caller(0);
    print "package = $callpack\n";
}

package Package_1;
sub test {
    my($package, $filename, $line) = caller;
    print "package = $package, filename = $filename, line = $line\n";
}
sub test2 {
    CALL::test;
}

package main;
CALL::test;
Package_1::test;
Package_1::test2;

これを perl test_caller.pl でテストした結果が次のようになります。

$ perl test_caller.pl 
package = main
package = main, filename = test_caller.pl, line = 18
package = Package_1

確かに caller の戻り値は パッケージ情報のようです。

3行目の my $pack = shift; は引数のリスト @_ から第一引数のパッケージ名を取り除きます。4行目の my @vars = @_ ? @_ : keys(%ENV); では @vars に 引数のリストがあればそれを、なければ環境変数全部の名前のリストをいれます。5、6行は省略。

7行目の eval "package $callpack; use vars qw(" . join(' ', map {'$',$_} @vars). ")"; が問題です。eval 関数は次に来る式を実行してエラーがなければ $@ = null をかえします。エラーがあれば $@ にエラーメッセージがはいります。eval 関数のあとの式はスクリプトのコンパイル時には実行されず、実行時に動的に Pers スクリプトをコンパイルして実行する必要があるときに使われます。したがって eval の後の式が展開されて実行されることになります。

package $callpack; は import メソッドが main パッケージから呼び出された場合、package main; と読み変えられます。次の qw( は()内のリスト表記が、デリミターをスペースにして表現されると言うことを示します。つまり、qw( item1 item2 item3 ) は (item1, item2, item3)と同じリストになります。

つぎの join(' ', map {'$', $_ } @vars) が分かりにくいのでこれを真似してテストしてみます。テストのために次のようなファイル test_map.pl を作成します。

#!/usr/bin/perl
@vars = (a, b, c);
$line = join( ' ', map { '$' . $_ } @vars );
print "$line\n";

テストしてみます。

$ perl map_test
$a $b $c

これで qw( 以下の式が環境変数の名前のリストををとりだして、名前の先頭に $ をつけるという動作をすることが分かりました。前後しますが use vars ( LIST ) は LIST に記述された変数名をグローバル変数として宣言する関数です。したがって難関の7行目は環境変数と同じ名前を持ったグローバル変数を main に導入することが分かりました。

9行目は、7行目の eval が成功しなければプログラムを中止します。

11行目の tie 関数が難物です。tie 関数のためには独立した perltie という man ページがあるくらいです。tie 関数は普通はデータベースとPerl の変数とを結びつける働きがあります。tie VARIABLE, CLASSENAME, LIST というふうに使います。VARIABLE は CLASSNAME のメソッドと関係づけられ、LIST は VARIABLE と結びつけられる(隠された)オブジェクトを作るためのコンストラクター TIESCALAR に渡される引数です。変数 $variable が tie でパッケージ CLASSNAME に結びつけられると、$a = $variable; という式が評価される場合、パッケージ CLASSNAME の FETCH メソッドが実行されて、その戻り値が $variable の値となり、$a に代入されます。

Env の場合は tie $main::LANG, Env, LANG のようになりますから、まず 'LANG' がTIESCALAR メソッドに渡されて、LANG へのブレスされたリファレンスが戻されます(隠しオブジェクトの作成)。main パッケージで $a = $LANG; が実行されると、Env パッケージの FETCH メソッドに 'LANG'へのブレスされたリファレンスが渡され、sub FETCH の $self の値が 'LANG' へのリファレンスになります。したがって、$ENV{$$self} で$ENV{LANG} の値が戻り、$a に代入されることになります。

ここで、次のような小さなテストプログラム test_tie.pl を作成して tie 関数の動作を見てみましょう。

#!/usr/bin/perl
package Sample;

sub TIESCALAR {
    bless {};
}

sub FETCH {
    return 'hello';
}

package main;
tie $a, Sample;
print "$a\n";

TIESCALAR でオブジェクトを作成して、FETCH で 'hello' という文字列を返すようにします。試してみましょう。

$ perl test_tie.pl
hello

うまくいったようです。標準パッケージのハックは大変ですが、入門書には出て来ないような関数とその使い方の例を知ることができるので有益です。

最初の Env のテストプログラムで use Env; のあと import 関数を明示的には呼び出していませんが、次の章の use 関数でも述べるように import 関数は use Env; で自動的に呼び出されます。

perldoc について

perldoc を使うと Perl モジュールの検索が楽です。perldoc の使い方は

$ man perldoc

で分かります。また、モジュールの使用法を知りたいときは次のように perldoc <モジュール名(.pm はつけない)>で読むことができます。

$ perldoc Env

コードも含めたモジュール全部の内容を知りたいときは -m オプションをつけて perldoc -m <モジュール名> とします。

$ perldoc -m Env

モジュールの所在だけを知りたいときは -l オプションを使います。

$ perldoc -l Env

Perl の関数が分からないときは、preldoc -f <関数名> とします。説明が長い場合は less にパイプすることもできます。

$ perldoc -f caller | less