• 差分
  • リロード
  • 一覧
  • 最終更新のRSS

Perlについてのちょっとした知識。

ループ内のI/O演算子(<>)の自動definedチェック

  • whileforループでI/O演算子を使って行単位でデータを読み込むお馴染みの処理がある。
    while(my $line = <STDIN>) {
        ...
    }
  • こういった場合、最近のPerlではループ条件式を自動的にdefinedでチェックするらしい。つまり上記の処理は
    while(defined(my $line = <STDIN>)) {
        ...
    }
    と等価になる。
  • definedでチェックしないとどうなるかというと、例えばファイル終端に改行なしの"0"が書いてあった場合、 これがfalseとみなされてループを脱出してしまう。結果、"0"は処理されない。
  • 改行ありの"0\n"はtrueとみなされるので問題ない。
  • ちなみに、IO::Handleオブジェクトなんかで同じ処理を書いた場合は自動チェックは適用されない。
    my $io = IO::Handle->new_from_fd(fileno(STDIN), "r");
    while(my $line = $io->getline) {
        ...
    }
  • 参考: http://perldoc.perl.org/perlop.html#I/O-Operators

サブルーチンの入れ子

chompと$/

chomp関数は文字列末尾に$/特殊変数で示される文字列がある場合にそれを取り除く。 $/にexact matchじゃないと除去してくれないので注意。

よって、Windows環境で作ったファイル(改行コード: CR + LF)の各行をLinux環境(改行コード: LF)のPerlでchompすると、CRが残ってしまう。

文字列末尾の改行やスペースを取り除くなら下のやり方の方が安全か。

$line =~ s/\s+$//;

一つの正規表現パターンで複数回マッチ

一つの正規表現パターンに複数回マッチすることが見込まれ、 マッチした全ての部分文字列を取り出したい場合、以下のようにする。

@matchlist = ($testee =~ /パターン/g);

パターンの中に()で囲まれた部分パターンを入れておくと、 matchlist変数にそのパターンへマッチしたtestee変数内の部分文字列のリストが格納される。 パターンマッチ演算子にgオプションをつけておくのがミソ。 パターンには複数の()が含まれていてもいい。

1行if, 1行while

Perlではif節などの括弧{}を省略することができない。

つまり、

# if(!&check($hoge)) die "Error!";    ← エラー
if(!&check($hoge)) { die "Error!" }

てな感じなのである。

ただし、ifを1行で済ます文法もじつはちゃんとある。下のように書く。

die "Error!" if !&check($hoge);

こんなふうに、実行文の後ろにif 式をくっつける。 このような文の後ろにくっつく条件構文を文修飾子(statement modifier)というらしい。

statement modifierには以下の4つがある。(EXPRは式、LISTはリストを表す)

  • if EXPR
  • unless EXPR
  • while EXPR
  • until EXPR
  • foreach LIST

機能はなんとなく分かる。

foreachを使った場合、その文でLISTから取り出された要素にアクセスするには$_を使えばよい。

eachを使った繰り返しの注意点

while(($key ,$value) = each(%hash)) {
    ....
}

上のコードは連想配列の全要素を走査する常套手段だが、 このループをlastreturnで強制脱出すると大変なことが起きる。

どうも、eachでどのレコードを引っ張ってくるかはどこかに記録されているようで、eachを中途半端なところでやめた場合、次にeachを呼び出すとその次からレコードを取り出すようになる。

それだけならそれほど問題でもないが、eachを途中でやめて、その後またeachでレコードを取り出し続けると、なぜか取り出せないレコードが出てくるようだ。つまり、まだ取り出していないレコードがあるにもかかわらずeachのループが終了してしまう。

よって、eachを使うときは必ず全てのレコードを走査させなければいけない。

eachとforeachの機能

foreach $elem (@array)で配列中の各要素を参照できるが、これはC++言語風に書くと以下のようになる。

Type array[N];
int i;
for(i = 0 ; i < N ; i++) {
    Type &elem = array[i];
    ...
}

つまり、$elemarrayの要素のコピーではなく、そのエイリアスといえる。 言い換えると、foreachのブロック内部で$elemに対して上書き処理をすると、@arrayの対応する要素がちゃんと書き換わるということである。

一方、each関数はハッシュの要素対の値を返すのであって、エイリアスを返すのではない。 つまり、以下のようなコードを書いてもハッシュの値を変更することはできない。

while(($key ,$value) = %hash) {
    $value += 50;
}

ハッシュの値を変更したければこのように書かなければいけない。

while(($key ,$value) = %hash) {
     $hash{$key} += 50;
}

requireの探索ディレクトリ

スクリプト中では@INCという配列にrequireの探索元ディレクトリが格納されている。

コマンドラインからPerlを実行する時に、

perl -I [ディレクトリ] [スクリプトファイル名]

として実行すると、@INC-Iオプションで指定したディレクトリを追加できる。

また、環境変数PERLLIBにディレクトリ名を書き込んでおくとそのディレクトリはデフォルトで@INCに追加される。 環境変数を書き出すには……まあ、OSによってもいろいろ方法が違う。

正規表現の"."

"."は任意の文字にマッチするメタ文字だが、改行文字にはマッチしてくれない。 マッチ修飾子"s"を使うと改行文字にもマッチするようになる。

例:

    $str =~ s/<p>(.*?)<\/p>/$1/gs;
Last-modified: 2013-02-10 (日) 22:18:20 (4085d)