Tips & Information 今回のお題:
『perl に関するメモ』


●動機

「なんとなくメモ」に書きっぱなしにしておくのもなんなので。

●内容

require 文で呼ばれるファイルの末尾

require 文で呼ばれるファイルは、末尾で評価された式が真でなくてはならない。 ファイルの尻に 1; とか書いておくこと。


=> 演算子

演算子 =>, (カンマ) と同じ意味。 ハッシュをリテラル形式で書きたいときに使うと良い。

%somehash = ( time=>'tashiro', onigiri=>'washo-i' );

などと使うと見易くなるかもしれない。


ハッシュのキーの表記法(クォート囲みの有無)

ハッシュのキーは、基本的にはクォートで囲んでも囲まなくても同じなのだが、 キーの頭文字が数字の場合は囲む必要がある。常に囲っておくという方法もあるが、 => 演算子を使ったときの見易さの向上度合いが低いのが難点。


個数範囲指定のマッチング

$hoge =~ /^(x{3,10})/;

($hoge の先頭から、3 個ないし 10 個、文字 'x' が連続している) というマッチで、"3," と "10" の間にスペースを入れてはいけない。


package 内の my 変数は、外から参照できない

my で宣言された変数のスコープは、その時点の package 内のみに限定される。

たとえば、package 宣言を含むファイルを require して、 その中の変数を $packagename::variable という具合に参照する場合、 変数が my variable; と宣言されているとハマる。 この場合は、(もちろん、require されているファイル内の)宣言の my を外す。 use strict; としている場合、これも外なければならないが仕方がない。


qq は便利

qq(hoge)"hoge" と等価だが、 " をエスケープせずに済むので、HTML を吐かせる時なんかは便利。

print qq(<tr><th abbr="number">数</th><th abbr="OS">OS</th><th abbr="\%">割合</th></tr>\n);

ここで () は、||// なんかでも構わない。 つまり、qq は、直後の 1 文字を " の代わりにして文字列を記述するために使われる。 ただし、(), <>, {}, [] は 2 つで 1 対にして使われる。 また、スペース、_(アンダーバー)、アルファベット、数字は使えない。 よく使われる記号は /, |, :, ! あたりかな ?

もちろん、" の「代わり」にするだけなので、qq|〜| 等とした場合に、 〜 の中に "|" が含まれるならエスケープしなければならない。 また、変数展開も有効であるため、"%" が含まれる場合も同様にエスケープする。

同様のことを 'hoge' に対して行いたい場合は、q(hoge) とする。

jcode.pl では、ファイルの頭の方で $rcsid = q$Id: jcode.pl,v …… $; とかやってました。


シンボリックリファレンス(とはあまり関係ないところ)でハマった

# 上手くいかないコード
($hensu1, $hensu2, $hensu3) = ('foo', '', 'bar');
@sr_list = ('hensu1', 'hensu2', 'hensu3');

foreach (@sr_list) {
  if (!$$_) {
    my $rs = sub { print "$_ が空です.\n" };   # ここで $_ を使ってるのがマズい ?
    push(@error_list, $rs);
  }
}

foreach (@error_list) {
  &$_();
}

シンボリックリファレンスを用いて、変数が空だったら、 その旨を表示するサブルーチン(というかクロージャ)へのポインタをリスト @error_list へ push するというコードを考えている。

前の foreach で変数のチェックとリストへの push、 後ろの foreach でクロージャを呼び出してエラーメッセージを表示する。

……はずなんだけど、上のコードでは上手くいかず、

CODE(0xba6004) が空です.

等と出力されてしまう。正しいコードは以下の通り。

# 上手くいくコード
($hensu1, $hensu2, $hensu3) = ('foo', '', 'bar');
@sr_list = ('hensu1', 'hensu2', 'hensu3');

foreach (@sr_list) {
  my $sr_name = $_;          # いったん待避させて使う
  if (!$$sr_name) {
    my $rs = sub { print "$sr_name が空です.\n" };
    push(@error_list, $rs);
  }
}

foreach (@error_list) {
  &$_();
}

sub {} の中で、$_ の指すモノが変わってしまったのが原因か。


ハッシュの内容をソートされた順に処理

ハッシュの全てのキーと値のペアを得る each() の出力は、 突っ込んだ順などとは関係なくバラバラの順で出てくる。

ソートされた順にキーと値のペアを得て処理したい場合、 例えばハッシュ %h のキーを、対応する値でソートして得たいなら、

foreach (sort { $h{$b} <=> $h{$a} } keys %h) { ... }

とする。


マッチ演算子が返す値

マッチ演算子にオプション g をつけると (//g) どうなるかという話。 リストコンテキストで評価すると、マッチした部分全てをリストにして返す。

$str = '123 4567 abcd efg';
@list = $str =~ /\b\w{3}\b/g;

とすると、@list の内容は (123, efg) となる。

スカラーコンテキストで評価すると、普通に真偽値を返す。 つまり、マッチしていれば 1 を、さもなければ空文字列を返す。

$str = '123 4567 abcd efg';
$flag = $str =~ /\b(\S{3})\b/;

とすると、$flag には 1 が入る。

ここで ($flag) = ... とすると、 $flag には '123' が入る(リストコンテキストだから)。


バイナリデータの入出力

UNIX では特に意識する必要はない。 Windows では、入出力をバイナリモードで行う必要がある。 Windows でのデフォルトは ASCII モードで、このまま入出力を行うと、 勝手に改行コード変換が行われてしまい、データが正常に読み書きできない。

バイナリモードでの入出力を行うには、binmode() を使う。 引数はファイルハンドル。

open(OUTFH, "> hoge.dat");
binmode(OUTFH);   # set filehandle to binary mode
print OUTFH pack("C*", 0x01, 0x02, 0x00);

文字(列)数を数える

$_ = "abracadabra";
$cnum = tr/a//;   # count num of 'a'

正規表現の tr は、文字(文字列ではない。文字群?)の変換を行う。 tr/a-z/A-Z/ なんていうのをよくやるが、後のが空の場合、 変換は行われない。で、tr は変換「しようとした」文字の数を返すから、 これでカウントが可能。

tr はほかの正規表現と違って、^ とか $ とかの特殊文字が使えない (ハイフンによる範囲指定は使える)。変数展開もされないので、 変数に入ってる文字(列)の数を数えたい場合は次のようにする。

$str = 'abracadabra';
$c = 'a';
$cnum = $str =~ s/$c/$c/g;   # $cnum = 5

$cnum = $str =~ /$c/g; ではダメなことに注意。 $cnum = @tmplist = $str =~ /$c/g; なら O.K.。 scalar() のリスト版ってないモンかなぁ……。


ヒアドキュメントの終端文字列は重複してもいい

ヒアドキュメントの終端文字列は、ファイルとかパッケージとかの中で unique だったりする必要はない。次のようなコードでも全く問題はない。

print <<'END';
0
END
print <<'END';
1
END
print <<'END';
2
END

使用可能な全てのモジュールのリストを得る

プロバイダの WWW サーバでどんなモジュールが使えるか調べたいときなどに。

% find `perl -e 'print "@INC"'` -name '*.pm' -print

shell が使えない場合は、perl で shell もどきを作って置けばいいでしょう。 受けた文字列を system() で実行するだけ? find が使えない場合も同様に。

●あとがき

どんどん増える。多分。


戻る