読書ノート |
![]() |
| ▲ [ 国内 | 国外 | 記事 | 漫画 ][ Home ] ▼ [ 読感|抄録|正誤表 ] |
|
Effective Perl |
|
|
Joseph N.Hall, Randal L.Schwarts/吉川邦夫(訳) |
|
Effective シリーズの Perl 版。通常 Perl の本といえばラクダ本やリャマ本の版元であり Perl の作者たる Larry Wall も勤めるオライリーといいたいところだが、この本は別である。書名からも分かるように Scott Meyers の 『Effective C++』 と同様の体裁となっており、多岐にわたる機能を持った Perl の勘所を要領よく解説している。そのため、どちらかというと話題の幅広さに重きを置いた形式になっている。まあ、今なら類書が当のオライリーからも『Perl クックブック』として翻訳されているし、これで飽き足らないならば黒ヒョウ本(『実用 Perl プログラミング』)がある。
大抵、言語の文法だけでは実用レベルのプログラムは記述できない。そこで登場するのが、言語特有の落とし穴を解説し、ポイントに絞って解説する、この手の本となる。(それゆえ、ある程度の素養は必要だが)本書は Perl の場合における、そのための良書としておすすめできる一冊である。
この本は、読者の思考を促すように書かれている。例題の多くは、何か微妙な要素を含んでいる。本当にトリッキーな要素は説明するが、シンプルでありながら必ずしも明白ではない、その他の数多くのことは、わざと説明していない場合が多い。しばらく頭を悩ませるようなことがあっても、びっくりしないでいただきたい。Perl は一風変わった言語であり、いろいろな意味でほかのプログラミング言語とは非常に異なっている。流暢さとスタイルは、練習と反復によってのみ身に付くものであること、そして学習はハードワークだけれど、喜びもあれば実りも大きいということを、忘れないでほしい。
Perl での配列(array)とは割り当てられたデータのあるデータ構造であり、名前はある場合もない場合もある。これに対し、リスト(list)とは実行時のスタック上に置かれる一連の値を指す。Perl は配列とリストを交互に変換する。
Perl のスカラー、配列、ハッシュ、サブルーチン、フォーマット、ファイルハンドル、ディレクトリハンドルは、それぞれ独立した名前空間を持つ。配列やハッシュの中身を $ で参照するというのは、スカラーの参照ではなく結果がスカラーなのだということ。
Perl では配列要素(スカラー)を取得するには $array[$n] とする。これが @array[$n] の場合はスライスになる。スライスはリストであり左値としても利用できるが、スカラーコンテキストで使うとスライスの最後の値を返すという特徴がある。
そのため、要素がひとつのスライスを右値に使うと配列要素のようにふるまうが、これが左値だと最後の値しか残らない。また、@text[@text] = 'EOF'; というときには [ ] 内の @text がリストコンテキストで評価されることにより、左値は @text の内容(文字列)をインデックス(数値)として扱おうとするスライスになる。それは、ほとんどの場合 0 になるので、この @text は @text[0, 0, 0, ... ] となり、その最初の要素に EOF (結果的に undef)がセットされ、その他は 0 という結果を導く。このため単一要素のスライスは使わないようにする方がよい。
一方、スライスは結果から必要な要素だけを選択する場合や、値の交換、配列のソートなどで使うと便利である。
($uid, $gid) = ($stat($file))[4, 5]; // 要素の選択
@array[$m, $n] = @array[$n, $m]; // 値の交換
@name = @name[ # 配列のソート
sort {$uid[$a] <=> $uid[$b]} 0..$#name ];
@old{keys %new} = values %new; // 値の上書き
未初期化の配列は「( )」、即ち空リストであり、これを undef で代入すると「要素数 1 の値として undef を持つリスト」となり意味が変容する。そのため配列のクリアは @array = (); として空リストを代入する。配列を本当に縮小させたい場合も undef では無効なので、$#array に代入するか slice( ) や pop( ) などを使う必要がある。これはハッシュでも同様。
@array = undef;
if(@array) # これは真になる
{
・・・
}
文字列の比較演算子は値を ASCII 順に評価するので数値での評価とは意味が異なる。
基本は 0 と空文字列が偽で、その他は真になる。undef を判定する場合は defined( ) を使う。ハッシュなら exists( ) である。
数値を文字列にするときは sprintf( ) ($# に依存したくなければ gconvert( ))を使う。逆に文字列を数値にするときは atof( ) を使う。これは同名の C の標準ライブラリ関数と同様、文字列中の最初の数字部分だけを対象として扱う。なお、エラー変数 $! は数値コンテキストなら errno の値、文字列コンテキストなら errno に対応する perror( ) の文字列を返す。
以下に例を示す。$_ は main パッケージに存在するので、他のパッケージにいるときは注意しなければならない。$_ を局所化するには local を使う。
foreach $_ (@list) # $_ は省略可
{
&do_something($_); # $_ は省略不可
}
while(defined($_ = <STDIN>))
{
print $_;
}
while(<STDIN>){print} # 上と同じ
shift( ) のデフォルト引数はサブルーチン内なら @_、サブルーチン外なら @ARGV となる。また、-t 演算子は STDIN をデフォルトのファイルハンドルとして扱う。-t はプログラムが対話的に実行されているかどうかを判定するので、例えば CGI がコマンドラインから実行されたのならデバッグモードに移行するなどという使い方ができる。
my @array = @{shift()}; # リファレンス引数をデリファレンス
for(<*.c>) # foreach と同じこと
{
$bytes += -s;
}
# パターンマッチからスカラーコンテキストを得る
@words = split /\+/, (/[^:]*/)[0];
($str) = /[^:]*/; # 上と同じ
@words = split /\+/, $str;
# リストリテラルを無名配列で直接取得
$wordlist_ref = [split /\+/, $str];
# リストの無名コピーの作成 (オリジナルを残しながらの処理)
# (リストを [ ] に入れてデリファレンスする)
@array = grep { s/\.c$/\.h/ and ! -e } @{[@cfiles]};
上記以外について。
閉じかっこ(})直前のセミコロン(;)は省略できるなど。
下記以外に read( )、sysread( ) によるブロック読み込みなど。
while(<FH>) # 1 行ずつ処理する
{
# $_ の処理
}
while (defined($line = <FH>)) # 「0」だけの行も扱う
{
# $line の処理
}
foreach (<FH>) # ファイル全体を読み込んでから処理
{
# $_ の処理
}
{
local $/; # 入力セパレータのクリアと局所化
$file = <FH>; # ファイル全体を単一の文字列扱い
}
foreach、map、grep を使うとリストを添字なしで処理できる。これらは以下のように使い分ける。
# ファイル名リストをファイルサイズリストに変換
@size = map{ -s $_ } @files;
# m// と map の併用
@stem = map {/(.*)\.txt$/} @files;
# grep の慣用句
print grep /^joseph$/i, @lines;
但し、map の中で $_ を書き換えると($_ が使われると) map されようとしているリストが変更されてしまうので注意が必要。これを避けるには $_ を使わないように明示的に my した変数を使い、リストを書き換えるのなら foreach を使う。また、grep の引数は map とは異なりスカラーコンテキストとなる。
文字列を囲むのに好きな文字を使いたい場合は q (シングルクォート)、qq (ダブルクォート)を使う。文字としてかっこを使った場合は対で使えばよい。この場合、Perl はかっこのネストを考慮する。ソースコードを引用したければ q{...}、qq{...} すればよい。
qq<Don't << quote >> me !>;
sort( ) は ASCII コード順にソートを行うので、異なるルーチンを使いたい場合はソートサブルーチンを使う。ソートサブルーチンでは引数が固定の $a、$b となるので($a、$b はこのために局所化されており use strict の例外でカレントパッケージに属する)、後は C の qsort( ) のように比較ロジックを書けばよい。もちろん、$a、$b を書き換えてはならない。
# 数値でのソート
@list = sort { $a <=> $b} qw(This is a pen);
# 大文字/小文字を区別せずにソート
@list = sort { uc($a) cmp uc($b) };
# 逆順にソート
@list = sort { $b <=> $a } @list;
# 複数のキーでソート
@index = sort
{
# or は左辺が真ならば右辺を評価する
$lastname[$a] cmp $lastname[$b] or
$firstname[$a] cmp $firstname[$b]
} 0 .. $#firstname;
# ファイル変更時刻でソート
@list = sort { -M $a <=> -M $b } @files;
# より高度な方法
@list = sort
{
# ||= は $m{$a} = $m{$a} || -M $a ということ
# 一度、$m{$a} がキャッシュされれば、
# 以降は || の右辺は評価されずに済む
($m{$a} ||= -M $a) <=> ($m{$b} ||= $m{$b})
} @files;
# より一般的にはシュウォーツ(Randal Schwartz)変換を使う
@list =
map { $_->[0] } # 4. 結果を取り出す
sort { $a->[1] <=> $b->[1] } # 3. デリファレンスを比較
map { [$_, -M] } # 2. 配列リファレンスに入れて
@files; # 1. 対象ファイルを
かっこ、アスタリスク、ピリオドなどを正規表現のメタキャラクタとして扱いたくないときは、quotemeta 演算子か \Q を使う。これらは \w 以外のキャラクタの前にバックスラッシュを挿入する。
特殊変数 $+ には空でない最後のメモリの値が入る。メモリ変数は新しいスコープのたびに自動的に局所化され、その際にはその外側での値が引き継がれる。メモリ変数($1、$2、$3 ...)に対して後方参照(\1、\2、\3 ...)は対応するメモリ内容にマッチする。置換文字列の中でメモリ変数を使うと直前の結果ではなくマッチした部分に対するメモリ参照となる。
/(\w)\1/; # 同じ文字の繰り返しにマッチ
マッチ演算子をリストコンテキストで使うと、メモリ変数に対応した値のリストを返す。更に g オプションを使うと、成功したマッチそれぞれの全てが返される。
# リストへの代入
($name, $value) = /^([^:\s]*):\s+(.*)/;
# map の中でマッチ演算子を使う
($date) = map { /^Date:\s+(.*)/ } @msg_hdr;
スカラーへの m//g マッチでは正規表現エンジンが直前のマッチの終了した箇所(マッチポジション)からマッチを再開する。これは pos( ) で取得することができる。更に c オプションと併用すると、マッチが失敗してもマッチポジションを保持することができる。
C のコメントを正規表現するには次のようにする。下の正規表現のしくみを理解するのは大変なので、このような場合は欲張らない演算子を使う。
s#/\*[^*]*\*+([^\*][^*]*\*+)*\##g;
\b にマッチするのは \w から \W への遷移であり、\s から \S への遷移ではない。空白で区切りたいのであれば以下のようにする。\b、\B は長さ 0 のパターンなので、マッチしたものがよく分からない場合は置換してみるのがよい。
$text =~ /(^|\s)\Q$word\E($|\s)/;
また、$ は正確には行末ではなく、マッチされている文字列の末尾か文字列の最後の改行の直前にマッチする。s オプションを使うと改行以外の全ての文字ではなく全ての文字にマッチする。文字列末尾へのマッチを厳密に行うのであれば、(?! ...) を使う。(?! ...) は $ の後ろに改行のないことを保証している。
s/.$(?!\n)/<end>/;
速度的にはパターンマッチが split ( ) よりも速いが split( ) の方が読みやすい。split( ) を繰り返して文字列を徐々に小さく分割することもできる。
($a) = (split /:/)[4]; # 5 番目の要素の取得
一方、カラムで区切られたデータの処理には unpack( ) で効率よく処理できる。特に空白ではなくカラム位置で split( ) したいときに向く。
# 例として ps の結果を取り込む
chomp (@ps = `ps l`);
shift @ps; # 最初の行を捨てる
for(@ps)
{
# @ 指定子は値を返さずに絶対位置へ移動する
# (@8 A6 は 8 の位置から始まる 6 文字を示す)
($uid, $pid, $sz, $tt) =
unpack '@3 A5 @8 A6 @30 A5 @52 A5', $_;
}
正規表現のマッチングはシンプルなものであっても必ずメモリ変数を操作するので、シンプルな文字列操作には index( ) (最も左側にあるもの)、rindex( ) (最も右側にあるもの)、substr( )、tr/// などを独自に使うべきである。具体的に以下が挙げられる。
ちなみに index( ) は Boyer-Moore アルゴリズムを用いており、非常に高速に検索を行うことができる。また substr( ) は左値として使うこともできる。更に tr/// は文字の個数を数えるのに最も速い方法でもある。
まず x オプションは空白を無視させる。(但し、直前にバックスラッシュのある場合か文字クラスの中にある場合を除く。)
リテラルのバックスラッシュは \\ で、変数中のバックスラッシュは更に 2 重に \\\\ で 1 個のバックスラッシュにマッチする。このようなバックスラッシュを含むパターンなど複雑な正規表現は変数で置き換えるのがよい。
$back = '\\\\';
$spec_ch = "$back\\W"; # エスケープされた文字
$hex_ch = "${back}x[0-9a-fA-F]{2}"; # 16 進エスケープ
$oct_ch = "${back}[0-3]?[0-7]?[0-7]"; # 8 進エスケープ
$char = "[^\"$back"]"; # 通常の文字
($str) =~ /(
"(
$spec_ch | $hex+ch | $oct_ch | $char
)*"
)/xo;
上のような定義によってどのような正規表現が得られたかを知りたいときは、それをプリントすればよい。
print <<EOT;
/(
"(
$spec_ch | $hex+ch | $oct_ch | $char
)*"
)/xo;
EOT
パターンに変数が含まれているとパターンを使うたびにそれが再コンパイルされる。これを避けたい場合は o オプションを使えばよい。これは置換でも利用できる。また、文字列クラス([abc])の代わりに選択(a|b|c)は使わないようにする。
my 演算子は字句解析的(lexical)スコープを持つ変数を作成する。my 変数はパッケージのシンボルテーブルに含まれないので、(シンボルテーブルに含まれるソフトリファレンスや型グロブを使っても)アクセスすることはできない。一方、local 変数の場合は宣言する前の値が実行時のスタックに保存され、それから新しい値で上書きされる。そして、ブロックを抜けると保存されていた値が復元される。local はコンパイル時ではなく実行時の機構なので、local のスコープ外からでも値の書き換えを見ることができる。
通常、my と local では my を使うようにする。理由は my の方が local より速く、局所性にまつわる問題も回避できるため。また、Perl のクロージャ(closure)は my をベースにしているため。
@_ はコピーしたものを使った方がよい。理由として名前を付けた方が理解しやすい点、@_ はエイリアスであり @_ を書き換えると対応する引数も書き換えられるが値が read-only の場合にはエラーとなる点が挙げられる。なお、引数なしで呼び出されたサブルーチンにも空の @_ は渡される。また、サブルーチンを & 付きの ( ) なしで呼び出しと、現在の @_ が引き継がれる。
サブルーチンの呼び出し側が戻り値として期待しているコンテキストを知るには wantarray( ) を使う。これは、もしそのサブルーチン呼び出しがリストコンテキストであれば真を返す。
引数はリファレンスで渡した方が値を変更できるだけでなく、@_ へのコピーのコストを避けることができる。ハッシュでもハッシュそのものよりリファレンスを渡した方が(キーと値への分割などが行われないので)効率的になる。一方、デリファレンスに時間がかかったり $ の増えるのが嫌ならば、local で別名を付ければよい。
sub f
{
loca(.a, .b) = @_; # 別名を付ける
・・・ # 以降は @a、@b でそのまま利用できる
}
また、ファイルハンドルやディレクトリハンドルを渡す場合は、型グロブをリファレンスで渡したり、FileHandle モジュールや DirHandle モジュールを使う方法がある。しかし、これらは今なら IO::File、IO::Dir を使うのがよい。
# 型グロブのリファレンスを使う
sub fh_by_globref
{
my $fh = shift; # デリファレンスが不要
print $fh "message\n";
}
open FILE "> tmp.txt" or die $!;
fh_by_globref \*FILE;
# IO モジュールを使う
use IO::File;
sub fh_by_IOFile
{
my $fh = shift;
print $fh "message\n"
}
$file = new IO::File "temp.txt", "w";
die "couldn't open : $!" unless $file;
fh_by_IOFile $file;
Perl でのパラメータはファイルテスト演算子との混同を避けるためにも 1 文字(-x など)にしないようにする。
# 名無しハッシュを用いたパラメータの解析サブルーチン
sub do_params
{
my $arg = shift; # 引数リストのリファレンス
my @defaults = @{shift()}; # パラメータ名とデフォルト値の配列
my %param;
if(ref $$arg[0] eq 'HASH') # 名前付きパラメータで
{
%param = (@defaults, %{$$arg[0]});
}
else # 位置によるパラメータ
{
my $n = 1;
my @arg = @$arg;
while(@arg)
{
$defaults[$n] = shift @arg;
$n += 2;
}
%param = @defaults;
}
return \%param;
}
# 呼び出し側
sub uses_anon_hash_params
{
my $param = do_params( # ハッシュリファレンスを返す
\@_,
[foo => 'val1', bar => 'val2']);
for(keys %$param)
{
print "$_ : $$param{$_}\n"; # $$param{foo} として取り出し
}
}
(あまりまだ正式な機能ではないようなので割愛する。)
サブルーチンリファレンスを返すには \ でか名無しサブルーチンを使えばよい。返されたら -> で呼び出せばよい。名無しサブルーチンは動的に生成できるので C の関数ポインタというよりは LISP 的なものといえる。
$func = code_ref(); # サブルーチンリファレンスを返す $func->(); # リファレンスをデリファレンスして呼び出す
名無しサブルーチンの中で my 変数を使うと、スコープへ入るごとに異なるコピーが作成される。これをクロージャ(closure)と呼び、これによってスコープを外れてからでもアクセスできるプライベートな変数を実現することができる。更にクロージャと eval によってオブジェクト指向的な実装を行うこともできる。
# クロージャと文字列 eval による効率的なパターンマッチ
sub make_grep
{
my $pat = shift;
# o オプションはサブルーチン呼び出しごとに 1 回
eval 'sub{ grep /$pat/o, @_}';
}
# サブルーチンリファレンスの取得
$find_us = make_grep q/(?i)\b(joseph|randal)\b/;
@found = &$friend_us (<STDIN>); # マッチするものを探す
デリファレンスの添え字付けには -> を使うのが最もよい。これは連鎖して使用でき、また矢印の両側が共に添え字の場合は省略できる。リファレンスは基本的にスカラーとして扱われるが自身でオブジェクトの型に関する情報を持っており、これは ref 演算子で取得することができる。(リファレンスをハッシュキーにした場合はリファレンスでなくなってしまうので、Tie::RefHash を使う必要がある。)
値が未定義なスカラーを左辺値としてあたかもリファレンスのように使うと、Perl は適切なオブジェクトへのリファレンスを生成する。これを自動活性化(Auto Vivification)という。
undef $ref; $ref->[3] = 'four'; # ref の自動活性化
また、文字列値をデリファレンスして指定された名前の値を得ることをシンボリックリファレンスという。但し、これは参照カウントとは関係がない。これは strict refs で無効になる。
$str = 'pi';
${$str} = 3.14;
print "pi = $pi\n"; # pi = 3.14
リファレンスを使うとネストしたデータ構造を容易に作ることができる。これらは事前にその要素を宣言しなくても使用された時点で必要な領域が確保される。
違いとして、名無し配列コンストラクタ([ ])はリストコンテキストを作るが、かっこ(( ))はリストコンテキストを作らないなどがある。
Perl は C のような構造体を持ってはいないがハッシュを使うことで似たものを得ることはできる。
$student = {}; # 空の構造体のイメージ
$student{num} = 100; # ハッシュの文字列をメンバ名的に扱う
・・・
循環構造のデータ型を作ってしまうとオブジェクトの参照カウントが 0 にならない結果、それはプログラムの終了するまで消えなくなる。これを避けるには循環データ構造へのリンクををひとつ変数として保持しておき不要になった時点でリンクを切る、2 パス形式でまず破壊する必要のあるリファレンスへのリファレンスを収集してから、後はそのリストをたどって破壊する――がある。(特に複雑な場合は後者が最も効率的といえる。)
package main
{
my $a = New circular 'a';
my $b = New circular 'b';
$a->{next} = $b;
$b->{next} = $a;
$head + $a; # リンク
}
undef $head->{next}; # リンクを切る
undef $head; # 削除
例えば for ループを使って複雑なデータ構造から部分を取得・設定する場合には map を使う。(map の中でネストしたデータ構造の最初の項目だけ抽出したり、[ ] を使って更にネストさせる。)また、選択を行う場合には grep を使うのがよい。map と grep は両用することもできる。
# @xyz の各名無し配列から最初の要素を選択
@x = map { $_->[0] } @xyz;
# @x、@y、@z を @xyz としてネストさせる
@xyz = map { [ $x[$_], $y[$_], $z[$_] ] } 0 .. $#x;
# y > x なものを選択
@y_gt_x = grep { $_->[1] > $_->[0] } @xyz;
use strict; は strict vars、strict subs、strict refs を組み合わせて提供する。一時的に無効化したい場合は no strict すればよい。
# 変数宣言の強制
use strict vars;
# 対象スコープ中で strict vars を無効化する
no strict vars;
# 遅延ロードされる変数に対する事前の宣言
use vars qw($x);
BEGIN
{
$x = 100;
}
print $x; # そのまま利用できる
# 文字列の変数扱いを禁止
# (ハッシュキーと => の左は除外)
use strict subs;
for(my $i = 0; $i < 10; $i++)
{
# use strict subs がないと i は
# $i (数値としては 0)に解釈される
print $y[i]; # エラー
}
# シンボリックリファレンスの無効化
use strict refs;
一方、動的なチェックには -w が使える。(コマンドラインからつけてもよい。)一時的に無効化したい場合は $^W を local で使う。-w は若干のオーバーヘッドがあるので使用するのは開発中のみがよい。一方、use strict は残っていても問題はない。
{
loca $^W = 0; # スコープ内で警告を無効化
・・・
}
setuid を実行するプログラムでは -T で汚染チェック(taint checking)を有効にできる。但し、汚染チェックはあくまでも自分で書いた同じ結果を招くコードまでは防がないので注意が必要。これは主に CGI プログラムで効果がある。(nobody 以上のユーザになる setuid をチェックできるから。)
# 警告やエラー出力の饒舌化 use diagnostics; # 饒舌モード $^W = 1; # 警告 ON
# Benchmark によるベンチマーク (動作未確認)
use Benchmark;
my @a = (1..10000);
timethese(100,
{
# コードを q{} でシングルクォーテーション文字列化
for =>
q{
my $n = @a;
for(my $i = 0; $i < $n; $i++)
{
$a[$i]++;
}
},
foreach =>
q{
foreach $a (@a)
{
$a++;
}
}
})
# Data::Dumper によるデータのダンプと復元
# (これは自己参照的なデータ構造にも利用できる)
use Data::Dumper;
my $a = {A => 1, B => 2, C => 3, D => 4};
my $b = {E => 5, F => 6, G => 7, H => 8};
# $a というテキストストリームの実体は Perl なので
# それを評価するだけで元のデータ構造を復元できる
print Dumper $a;
# 名前を付ける場合
print Data::Dumper->Dump([$a, $b], [qw(a b)]);
# Devel::Peek による詳細なダンプ use Devel::Peek qw(Dump); my $a = 1234; "$a"; Dump $a;
また、Devel::DProf によるプロファイリングを行うには、実行時に -d:DProf オプションをつければよい。すると tmon.out ファイルが作成されるので、dprofpp プログラムでデータを分析することができる。詳細はマニュアルを参照すること。
Perl のデバッグバージョン(-D オプション)を使うには自分でソースからビルドを行う必要がある。そして Configure を実行したら非デバッグバージョンとは異なるパスにインストールし、最適化(-O)は無効、デバッグオプション(-g)を有効にする。また、追加コンパイルフラグ(cc flags)には必ず -DDEBUGGING を指定し、可能ならば -DDEBUGGING_MSTATS を追加する。(-DDEBUGGING_MSTATS が有効の場合は malloc に Perl のものを使うかという質問で y と答えること。) Configure 終了後は、make、make test、make install すればよい。Perl のバージョン更新時に configure の質問を繰り返したくない場合は、config.sh 中のバージョン番号を置換後、Configure -d すればよい。なお、デバッグオプションは $^D に整数の引数を渡しても内容を制御できる。
$^D = 14; # デバッグ・オン ・・・
デバッガでコマンドとして解釈されない文字列は Perl のコードとして実行される。特にいろいろなテストを実行する際には x コマンドが便利。
つまり、少しずつ頻繁にテストする。また、どこで失敗しているかをまず突き止めるようにする。また、異常に時間のかかるデバッグでは、原因はプログラムよりも当たり前のことの見落としであることが多い。
CPAN モジュールがインストールされていれば対話的に使うこともできる。(perl -MCPAN -e shell。)モジュールのインストールは「install <module-name>」コマンドで行う。CAPN モジュール自身の再スタートは reload CAPN でよい。また、CPAN に接続できない場合は自分でビルドしてもよい。このとき「perl Makefile.PL」実行時に「-LIB <directory>」オプションでインストール先を変更することができる。
perl Makefile.PL # 解凍したディレクトリで makefile を作成 make # make する make test # テストする make install # インストールする
Perl のパッケージは名前空間(シンボルテーブル)で、標準では main パッケージとなる。package 宣言は入れ子にできる。一方、モジュールとはいくつかの条件を満たしたパッケージのことを指す。具体的には、.pm のファイル名、import メソッドの定義、自動で export されるシンボルのリストと要求によって export されるシンボルのリストの定義が必要となる。モジュールも入れ子にでき、その場合は入れ子となったディレクトリに格納される。
Perl はモジュールの検索を include パスより行う。(perl -V で確認できる。) use は実行時ではなくコンパイル時の機構なので、これを変更したい場合は lib プラグマモジュールを使う。または実行時の -I オプションなどがある。
use lib "/share/perl"; # パスの追加
perldoc は「perldoc -f <function>」である関数についてのドキュメントを得ることもできる。
モジュールのボイラープレート(テンプレート)を生成するのに h2xs を汎用的に利用することができる。
h2xs -A -X -n <module-name>
-A : 関数オートロードのコードを生成しない
-X : 一般モジュールであることの明示
-n : モジュール名の指示
# サンプルの雛形
# Cmp.pm
package File::Cmp;
use 5.006;
use strict;
use warnings;
require Exporter;
# Exporter の継承
our @ISA = qw(Exporter);
# Items to export into callers namespace by default.
# Note: do not export names by default without
# a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public
# functions/methods/constants.
# This allows declaration use File::Cmp ':all';
# If you do not need this, moving things
# directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
# 自動的に export させたい関数のリスト
our @EXPORT = qw(
cmp_file
);
# バージョン番号
our $VERSION = '0.01';
# Preloaded methods go here.
# 関数の挿入
sub cmp_file
{
my ($file1, $file2) = @_;
local(*FH1, *FH2);
# ファイルがなければ NG
return -1 if ! -e $file1 or ! -e $file2;
# ファイル名が等しければ同じ
return 0 if $file1 eq $file2;
open FH1, $file1 or return -1;
open FH2, $file2 or close(FH1), return -1;
return 1 if -s FH1 !- -s FH2; # サイズが異なれば違う
my $chunk = 4096; # 一度に読み込むサイズ
my ($bytes, $buf1, $buf2, $diff);
while($bytes = sysread FH1, $buf1, $chunk)
{
sysread FH2, $buf2, $chunk;
$diff++, last if $buf1 ne $buf2; # 違っていれば抜ける
}
close FH1;
close FH2;
$diff; # 差分結果を返す
}
1;
__END__
# Below is stub documentation for your module.
# You better edit it!
# POD 用のドキュメントの雛形
=head1 NAME
File::Cmp - Perl extension for blah blah blah
=head1 SYNOPSIS
use File::Cmp;
blah blah blah
=head1 DESCRIPTION
Stub documentation for File::Cmp, created by h2xs.
It looks like the author of the extension was negligent
enough to leave the stub
unedited.
Blah blah blah.
=head2 EXPORT
None by default.
=head1 AUTHOR
A. U. Thor, E<lt>a.u.thor@a.galaxy.far.far.awayE<gt>
=head1 SEE ALSO
L<perl>.
=cut
# test.pl
# Before `make install' is performed this script should be
# runnable with `make test'. After `make install' it should
# work as `perl test.pl'
#########################
# change 'tests => 1' to 'tests => last_test_to_print';
use Test;
#BEGIN { plan tests => 1 };
BEGIN { $| = 1; print "1..3\n" };
use File::Cmp;
ok(1); # If we made it this far, we're ok.
#########################
# Insert your test code below, the Test module is use()ed
# here so read its man page ( perldoc Test ) for help
# writing this test script.
# テストコードの記述
srand();
for($i = 0; $i < 10000; $i++)
{
$test_blob .= pack 'S', rand 0xffff;
}
$test_num = 2;
eval # eval の例外機構を使う
{
# テストファイル作成
open F, '>xx' or die "couldn't create: $!";
print F $test_blob;
open F, '>xxcopy' or die "couldn't create: $!";
print F $test_blob;
open F, '>xxshort' or die "couldn't create: $!";
print F substr $test_blob, 0, 19999;
# テスト
if(cmp_file('xx', 'xxcopy') == 0)
{
print "ok ", $test_num++, "\n";
}
else
{
print "NOT ok ", $test_num++, "\n";
}
if(cmp_file('xx', 'xxshort') > 0)
{
print "ok ", $test_num++, "\n";
}
else
{
print "NOT ok ", $test_num++, "\n";
}
};
if($@) # eval からエラーが報告されていれば
{
print "... error: $@\n";
}
unlink glob 'xx*'; # 後始末
後は、これを perl Makefile.PL でビルドしてテストしてみる。そしてドキュメントを整え、Changes に作業内容を書いたら make tardist で配布ファイルを作成できる。これはこの時点で CPAN の規約を満たし、そのまま全世界に配布することもできる。
POD (Plain Old Documentation)は空行で区切られたパラグラフによって構成される。パラグラフには以下がある。
逐語的(verbatim)テキスト インデントされたパラグラフはそのまま再現される POD コマンド POD で意味を持つフォーマット指定コマンド フィルド(filled)テキスト 上記以外のパラグラフは通常のテキスト扱い
POD コマンドとして以下がある。
=head1, =head2 Level 1, 2 の見出し =item 1 番号付きリスト =item * 黒丸付きリスト =item B<NOTE> 太字のそのほかのリスト =over N N 個のスペースでのインデント =back インデントから戻る =cut POD の終わり =pod POD の始まり =for X 次のパラグラフは X フォーマット =begin X, =end X X フォーマットの最初と最後を囲む
フィルドテキストでの特殊フォーマットシーケンスとして以下がある。
I<text> 斜体 B<text> 太字 C<text> ソースコード S<text> テキスト内の空白で改行しない E<text> キャラクタエスケープ (通常は不要) L<text> リンクまたはクロスリファレンス F<name> ファイル名 X<text> 索引エントリ Z<text> 幅がゼロの文字
POD で man を書く場合には以下の約束事を守る必要がある。まず変数名、関数名を斜体とし、第 1 レベルの見出しには大文字を使う。そして、これは以下のような順のものを定義する。
NAME プログラム、ライブラリなどの名前 SYNOPSIS 使い方の簡単な例 DESCRIPTION 詳細な説明。必要に応じてセクション分けする EXSAMPLES 使い方の例 SEE ALSO ほかの man へのリファレンス BUGS 既知のバグ AUTHOR 作者の名前
XS を使うと Perl から呼び出し可能な C/C++ の関数(XSUB)を書くことができる。これは動的にロードされる共有ライブラリで、複雑かつ難解だが単純なものならば、x2hs と make だけで済ますこともできる。XSUB によって Perl への OS 機能の追加、処理速度向上で効果がある。XS に関しては perlxs、perlxstut、perlguts などが参考になる。
XSUB を書くときにも、まず h2xs でテンプレートと makefile を作成する。これをパッケージ名なしで呼び出したければ export する。
h2xs -A -n List::Shuffle perl Makefile.PL -- @EXPORT = qw(shuffle);
XSUB を記述するための XS 言語は一種の C プリプロセッサで .xs ファイルを XS コンパイラ(xsubpp)にかけると、XS を C のコードにコンパイルして Perl に対して必要なインタフェース(Perl 引数の C の型への変換など)を追加する。(実際には makefile が自動で呼び出してくれる。) XS ファイルは MODULE ディレクティブが XS ソースの開始箇所となる。以下に簡単なサンプルを示す。
char* # 戻り値型
realpath(filename) # 関数名とパラメータ
char* filename # パラメータの宣言
PRINIT: # CODE の扱う変数の宣言
char realname[1024];
CODE: # 計算部分のコード
RETVAL = realpath(filename, realname);
OUTPUT: # 呼び出し側に返される値のリスト
RETVAL # RETVAL は戻り値用のマジック変数
# realpath の呼び出し
$readname = realpath($filename);
コードは make でコンパイルし、test.pl にテストコードを書いたら make test すればよい。そして POD ドキュメントを用意する。
新しいモジュールに関する議論をする場合は request for discussion を comp.lang.perl.modules にポストすればよい。そのときには、その機能が既にあるかどうか、適切な名前・インタフェースかどうか、その実装技術があるかどうかはっきりさせる必要がある。その上で実際に開発をするのであれば、PAUSE (Perl Authors Upload SErver)に開発者として登録(名前やバンドル名、モジュールの概要などが必要)をする。そしてパスワードを設定したら、モジュールをアップロードし保守をする。
簡単なオブジェクト指向 Perl の例。Perl ではパッケージがクラスであり、パッケージに bless された「データ」がオブジェクトとなる。bless は(ハッシュ)リファレンスとパッケージ名を引数にする。(=オブジェクトは自分の属するパッケージを知っている。)そして、ref 演算子を bless されたリファレンスに使うとパッケージ名を返す。(つまり RTTI。)
# Timer クラス定義
package Timer;
sub new # コンストラクタ
{
my $pkg = shift; # 最初の引数がパッケージ名
# bless された名無しハッシュへのリファレンスを返す
bless { created => time }, $pkg;
}
sub DESTROY # デストラクタ
{
my $self = shift;
print "nuke '$self->{name}'\n";
}
sub Elapsed # メソッド
{
# 最初の引数はオブジェクト自身
my $self = shift;
# 作成時からの経過時間を返す
return time - $self->{created};
}
# Timer クラスを使う
package main;
$timer = new Timer;
sleep 5;
print "elapsed: ", $timer->Elapsed(), "\n";
print "timer is a: ", ref $timer, "\n";
メソッドとしてサブルーチン名の後にパッケージ名の続くとき、Perl はそれをパッケージ内のサブルーチンに第 1 引数としてパッケージ名を渡すものとして解釈する。これは一般的にクラスメソッドとなる。そして矢印による間接的なメソッドの呼び出しはでは -> の左側の値をサブルーチンに第 1 引数として渡す。つまり、これがオブジェクトメソッドという位置付けになる。
$timer = new Timer; # サブルーチン名 + パッケージ名
$timer = Timer::new('Timer'); # 上と同じ
# オブジェクトからのメソッド呼び出しには -> を使う
$elapsed = $timer->Elapsed();
また、C++ での this ポインタのようなものとして self 変数がある。これはメソッドを呼び出したオブジェクトへのポインタで、(メソッドの第 1 引数がオブジェクト = ハッシュリファレンスだから)第 1 引数を shift して $self にするのが慣例となっている。
Perl はデータ継承はがサポートしないが、単一継承、多重継承によるメソッド継承は直接サポートする。あるクラスにおける親クラスのリストは @ISA に格納される。呼び出されたメソッドが該当クラスにないとき継承元への検索が行われる。@ISA は実行時にも変更できるので、これを用いて動的なロードなどを行うことができる。そして、多重継承は @ISA に複数のクラスを書くだけで実現できる。この場合、親クラスは @ISA の記述された順に検索される。(つまり、深さ優先のアプローチになる。)検索を避けたい場合はパッケージ名で修飾したメソッドを呼び出せばよい。(カレントパッケージの場合は SUPER:: が使える。)
package B; @ISA = qw(A); # クラス B はクラス A を継承する
@ISA でメソッドが見つからないと、更に AUTOLOAD サブルーチンと UNIVERSAL クラスが検索される。AUTOLOAD サブルーチンはそのパッケージにないサブルーチン呼び出しで呼び出されるメソッドであり、$AUTOLOAD に呼び出されたサブルーチンの完全修飾名が入る。また、呼び出されたサブルーチンの引数が引数になる。そして、AUTOLOAD が呼び出されると DESTROY もオートロードされる。
sub AUTOLOAD
{
print "auto $AUTOLOAD\n"; # 呼び出し名
print "@_\n"; # 引数
}
そして、AUTOLOAD でも見つからないと、UNIVERSAL パッケージが検索される。
# どうしても検索されなかった場合
package UNIVERSAL;
sub AUTOLOAD
{
print "auto $AUTOLOAD\n";
}
Perl にはデータ継承のしくみがないが、これは特に問題とはならない。しかし、サブクラスのコンストラクタがデフォルト値を持つような場合、データ継承が意味を持つイメージになる。
package Text;
@ISA = qw(Graphic); # Graphic を継承する
sub new
{
my $pkg = shift;
# 自分のデフォルト付きで親クラスの
# コンストラクタを呼び出す
# (ここでのクラス引数は Text)
SUPER::new $pkg (
font -> 'times',
size -> 12,
@_);
}
# コンストラクタの呼び出し
$text = new Text(color => 'blue', font => 'courier');
Perl のタイ付き変数(tied variable)とは tie 演算子によってマジック属性を与えられた変数のことを指す。これはオブジェクトに結び付けられるので、この変数への演算はオブジェクトへのメソッドコールとして変換される。タイ付き変数としてスカラー、配列、ハッシュ、ファイルハンドルを使うことができ、メソッドとして TIEHASH (タイ付き変数を返す)、FETCH (値の取得)、STORE (値の代入)、DELETE (delete 呼び出し時)、CLEAR (ハッシュのクリア)、EXISTS (exists 呼び出し時)、FIRSTKEY (最初のキーを返す)、NEXTKEY (次のキーを返す)、DESTROY (デストラクタ)がある。
#!/usr/local/bin/perl -w
package FileProp;
use Carp; # croak によるパッケージ呼び出し箇所でのエラーの報告
my %PROPS = (
name => 1, size => 0, mtime => 1,
contents => 1, ctime => 0);
my @KEYS = keys %PROPS;
sub TIEHASH
{
my ($pkg, $name) = @_;
unless (-e $name)
{
local *FH;
open FH, ">$name" or croak "can't create $name";
close FH;
}
bless
{
NAME => $nmae, INDEX => 0
}, $pkg;
}
# タイ付き変数にタイする
tie %data, FileProp, "new.data";
# TIEHASH を呼び出す
FileProp::TIEHASH "FileProp", "new.data";
TIEHASH の戻り値はタイ付きき変数(%data)に結び付けられているので、%data へのアクセスは FileProp のメソッド呼び出しとなる。
pack には sprintf に似た部分があり、unpack はチェックサムの計算にも使うことができる。これは % とチェックサムのビット数の指定で行う。
unpack "%16c4", "\1\2\3\4"; # 10
また、インターネットアドレスのソートにも pack が使える。つまり、アドレスの数字を文字列として cmp すればよい。
@sorted_addr =
sort { pack('C*', split /\./, $a) cmp
pack('C*', split /\./, $b) } @addr;
# シュウォーツ変換で書き直し
@sorted_addr =
map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [$_, pack('C*', split /\./)] }
@addr;
更に、16 進エスケープの操作でも利用できる。例えば URI unescape は次のようにできる。(とはいえ現在では専門のモジュールが既に存在する。)ほかに uuencode/uudecode もできる。
$_ = "a%5eb"; # a^b
s/%([0-9a-fA-F]{2})/pack("c", hex($1))/ge;
Perl では実行時コンパイルの機構として eval を利用できる。コードを含む文字列を eval の引数として渡すと、文字列がコンパイルされてから呼び出し側のコンテキストで実行される。文字列は eval の実行ごとにコンパイルされ、最後に評価された式の結果が返される。
これに対してブロックを eval にかけることもでき、この場合は一度だけコンパイルされ、主に例外処理に用いられる。これはシグナルやパニックまで捕捉できるわけではないが、シグナルハンドラの追加は有益である。
eval
{
local $SIG{INT} = sub # Ctrl+C へのシグナルハンドラ
{
die "caught an interrupt";
}
my $foo = <>;
}
if($@) # Ctrl+C するとここに来る
{
print "error in eval : $@\n";
}
また、eval は require のベースともなっており、基本的に require はソースファイルを読んで eval を行っている。(ちなみに require はファイルを一度だけロードして include パスより検索し、引数に拡張子がなければ .pm と解釈する。)一方、do は require をよりシンプルで、結果の値によって動作が変わらず .pm を付けないが include パスは使う。これは何らかのファイルが Perl のコードであるとき、それをロードするのに利用できる。また、ブロックとしての do は必ず一度はブロックが実行されるので、do〜while でループにできる。
--config.dat $ROW = 25; $COL = 80; --プログラム die "config.dat not found." unless(-e "config.dat"); do "config.dat"; # ファイルを取り込む
基本的にいって、モジュールを利用できるときにはそれを利用するようにする。(socket、bind、connect してもよいがモジュールを利用した方がはるかに簡単。)もちろん、場合によっては自分で低レベルな部分を書かなければならないが、その場合でも最新のサンプルを使うようにする。(例えば Socket よりも IO::Socket を使うなど。)
また、サーバが子プロセスを生成して終了するとき、ゾンビを残さないためには SIGCHLD へのシグナルハンドラを使うのがよい。こうすると子プロセスが終了するとき、必ずこのシグナルハンドラが呼ばれるので、後は wait すればいい。
$SIG{CHLD} = sub{wait};
Perl によるネットワークプログラムのサンプル
# クライアント
use strict;
use Socket;
my $remote_host = shift or die "$0 : no hostname\n";
my $port = 2001;
# ホスト名を IP アドレスに変換
my $ip = inet_aton $remote_host
or die "unknown host : $remote_host";
my $proto = getprotobyname 'tcp';
# ソケットの作成
socket PSD, PF_INET, SOCK_STREAM, $proto or die "socket : $!";
# ソケット PSD での接続の確立
connect PSD, sockaddr_in($port, $ip) or die "connect : $!";
print while <PSD>; # リモートからの読み出し
close PSD or die "close : $!";
# サーバ
use strict;
use Socket;
my $port = 2001;
my $proto = getprotobyname 'tcp';
my $ps = '/usr/ucb/ps';
$SIG{CHLD} = sub { wait }; # ゾンビ対策
# ソケットの作成
socket SERVER, PF_INET, SOCK_STREAM, $proto
or die "socket : $!";
# 複数接続できるように SO_REUSEADDR をセット
setsockopt SERVER, SOL_SOCKET, SO_REUSEADDR, 1
or die "setsockput : $!";
# ソケットへ接続 (5 は慣習)
bind SERVER, 5 or die "listen : $!";
print "$0 listening to port $port\n";
for(;;)
{
# キューから CLIENT に接続をひとつ取る
my $addr = accept CLIENT, SERVER;
my $client_host = gethostbyaddr(
(unpack_sockaddr_in $addr)[1], AF_INET);
print "connection from $client_host\n";
# 受け付けたものの fork
die "can't fork : $!" unless defined(my $kid = fork());
if(not $kid)
{
my $option = <CLIENT>;
$option =~ tr/a-zA-Z//cd;
$option = "-$option" if $option;
print CLIENT `$ps'$option`;
exit;
}
else
{
close CLIENT;
}
}
例えばファイルサイズの取得は stat でなくても -s の方が簡潔だし速い。パーミッションのチェックでも -r、-w、-x で済む。
型グロブは基本的には使わないようにし、リファレンスで済むときにもそちらを使うべきである。その上で、型グロブの機能を説明すると、まず型グロブは別名をつけることができ、局所化することができる。また、型グロブをにリファレンスを入れることで特定の別名だけを定義することもできる。更に型グロブ添え字構文によって個々の型のリファレンスを取得することもできる。
# $aliase が $symbol の別名になる
*aliase = *symbol;
# シンボルテーブル参照の場合
$::{'aliase'} = $::{'symbol'};
# $aliase などが local
local *aliase = *symbol;
# シンボルテーブル参照の場合
local $::{'aliase'} = $::{'symbol'};
sub terminate { exit };
*finish = \&terminate;
$a = "string";
$sref = *a{SCALAR}; # $a のリファレンスを取得
文字列の中で式を使いたい場合は、配列コンストラクタ([ ])とデリファレンスを組み合わせることで実現できる。これはヒアドキュメントで式を挿入したい場合に効果がある。
$a = 2;
$b = 3;
print <<EOT;
$a + $b is @{[$a + $b]}
EOT
または、タイ付きハッシュを使う。こちらの方が記号の数は減るが、$print{ } の内部はスカラーコンテキストになる。
sub Print::TIEHASH
{
bless \ my $thingy, shift()
}
sub Print::FETCH
{
$_[1]
}
tie %print Print;
$a = 2;
$b = 3;
print << EOT
$a + $b is $print{$a + $b}
EOT
BEGIN はあらゆるコードよりも先に 1 度だけコンパイルされる。これはどこに書かれてもよいが、通常はそれの使われる場所(オブジェクト内部など)へ記述する。BEGIN を使うと、private/public な static 変数を実現することもできる。
# BEGIN を使う
sub dow
{
BEGIN
{
@dow = qw(Sun Mon Tue Wed Thu Fri Sat);
}
$dow[ $_[0] % 7 ];
}
# private な static 変数を作る
BEGIN
{
# @dow には sub dow からだけ可視でアクセスできる
my @dow = qw(Sun Mon Tue Wed Thu Fri Sat);
sub dow
{
$dow[ $_[0] % 7 ];
}
}
# public な static 変数を作る
BEGIN
{
# $static は inc_static と dec_static
# からだけ可視でアクセスできる
my $static = 10;
sub inc_static
{
++$static;
}
sub dec_static
{
--$static;
}
}
# BEGIN と require で他のソースから関数をインポート
# (use はこのしくみを利用している)
# func.pl
sub func
{
print "func\n";
}
# メイン
BEGIN
{
require "func.pl";
}
func1;
一方、END ブロックはプログラムの終了時に呼び出されるのでロックファイルやセマフォの後始末で使うとよい。
1 行野郎(one liner)によるコードの数々。
# ファイルハンドルのバッファリングの抑制
select((select(SOCK), $|=1)[0])
# 小さい方を返す
[$a => $b] -> [$b <= $a]
# 先頭から続く 0 をスペースに置換
s/\G0/ /g
# this と that を含むものへのマッチ
/^(?=.*?this)(?=.*?that)/
# 5 以外の数字へのマッチ
[^\D5]
# リストからエントリの重複の削除
@uniq = sort keys %{ {map{$_, 1} @list} }
# リスト項目をソートした場合の番号の取得
@rank[sort {$x[$a] cmp $x[$b]} 0..$#x] = 0..$#x;
# 文字列と数値の判定
"$_ is string\n" if(~$_ & $_) ne '0'
# 数字の 0 と NULL のチェックまで行う
perl -pe 's/\n/" " . <>/e' data
p.170 私4 -> 私
p242 A ps デーモン -> ps デーモン
▲ [ Top ]
|
Copyright (C) 2002-2006 唯野 Comment to Webmaster Last Modified 2002.11.24 Since 2002.5.21 Readed 2001.9.20 |