ばけらのPerlメモ

この文書の最新版は「鳩丸よもやま話 : ばけらのPerlメモ」に移転しています。以下の文章は古くなっているかも知れません。

CGI / perl 関係で気になった点、詰まった点などをメモしてみます。大崎さんのじゃなくて私のメモなんで役に立ちません。サンプルの実行結果などもあえて書いてなかったり。

真と偽

my と代入と式の評価

my による宣言や代入が成功したか否かが評価されるのではなく、変数の値が評価されます。

my $temp = 1 or print "1: fail\n";
my $temp = 0 or print "0: fail\n";
my $temp or print "fail\n";

$var =~ s/// の評価

置換に成功した個数が返ります。$result = $foo =~ s/人/もののけ/g; なんてのも良く使われます。tr も同様ですが、m は違います。m はマッチしたら 1, しなければ '' を返すだけで、マッチした個数は返さないので注意が必要です。

my $test = 'foo_bar_baz';

my $m = $test =~ /a/g;
print "m:$m\n";

my $tr = $test =~ tr/a//;
print "tr:$tr\n";

my $s = $test =~ s/a/a/g;
print "s:$s\n";

$mw++ while $test =~ /a/g;
print "m while: $mw";

0 と ''

0 と '' はどちらも偽なので注意。

sub foo{
 my $self = shift;
 my $foo = shift;
 if ($foo) {
  $self->[0] = $foo;
 }
 return $self->[0];
}

こんな風に書いてしまうと、$self->foo や $self->foo(5) などは期待通りに動作しますが、初期化しようとして $self->foo(0) や $self->foo('') としても $self->foo と同じ動作になってしまいます。そんなときは defined を使うと吉。

sub foo{
 my $self = shift;
 $self->[0] = $_[0] if defined $_[0];
 return $self->[0];
}

defined は hash にも使えます。

my %hash = ('foo' => 'bar');

print "1: $hash{'foo'}\n" if defined $hash{'foo'};
print "2: $hash{'bar'}\n" if defined $hash{'bar'};

$hash{'foo'} = '';
$hash{'bar'} = 'foo';

print "3: $hash{'foo'}\n" if defined $hash{'foo'};
print "4: $hash{'bar'}\n" if defined $hash{'bar'};

undef($hash{'foo'});
$hash{'bar'} = $hash{'foo'};

print "5: $hash{'foo'}\n" if defined $hash{'foo'};
print "6: $hash{'bar'}\n" if defined $hash{'bar'};

こんな使い方も……

use Jcode;
print defined &jcode ? "You can use jcode!\n" : "What?\n";

コンテキスト

配列やリストスカラコンテキストで評価する

配列をスカラコンテキストで評価すると、配列の大きさが得られます。しかし、リストをスカラコンテキストで評価すると、リストの最後の値が得られます。

my @test = (1,4,16);
sub test{ return (1,3,9);}

my $foo = @test;
print "$foo\n";

my($bar) = @test;
print "$bar\n";

my $foo2 = &test;
print "$foo2\n";

my($bar2) = &test;
print "$bar2\n";

いつも最初の値が得られると思ったら大間違いなので注意。

リファレンス

無名配列へのリファレンス。

my $foo = ['a', 'b', 'c'];
print "$foo\n";
print "@$foo\n";
print "$foo->[2]\n";

無名配列へのリファレンスを配列に入れることで多次元配列っぽいモノが実現できます。

my @foo = (['a', 'b', 'c'], ['as', 'bb', 'cc']);
print "$foo[0]->[1]\n";
print "$foo[1][2]\n";

最大の添え字を得る場合はこんな感じです。

my $foo = ['a', 'b', 'c'];
print $#$foo

書き方

一つのことを実現するのにいろいろな書き方ができる、というのが Perl の醍醐味と言うか売りと言うか。

こんなのはありがちな処理だと思います。

if($ID == 1){
 $foo = 'えび';
} elsif($ID == 2) {
 $foo = 'かに';
} elsif($ID == 3) {
 $foo = 'くらげ';
}

$ID ==1 と $ID == 2 が同時に成立することはないので、こんな風にも書けます。

$foo = 'えび' if $ID == 1;
$foo = 'かに' if $ID == 2;
$foo = 'くらげ' if $ID == 3;

発想を変えて、こんな風にも書けます。

$foo = ('', 'えび', 'かに', 'くらげ')[$ID];

こんなのもありますが、Perl のバージョンによってはエラーになる模様。5.6 では動きましたが、お勧めは出来ません。

$foo = qw(謎 えび かに くらげ)[$ID];

関数・命令文

last

last は一番内側のループを抜けます。

foreach my $i ('a'..'z'){
    print "$i:";
    foreach my $j ('a'..'z'){
        if ($j eq 'c'){
            last;
        }
        print "$j";
    }
    print "\n";
}

map

ワンライナーの旗手?

map {print "$_\n"} split("\0",$v);

ここでブロックの後ろに , をつけたりするとエラーになります。

splice

配列を割と自由に操作できる便利な関数です。

my @item = (0,1,2,3,4,5);
splice(@item, 1, 1, 'A','B','C');
print @item;

split

文字列を分解します。第一引数は正規表現なので注意。

my $foo = 'abc/+defg';
my @foo = split('/+', $foo);
foreach(@foo){
 print;
 print "\n";
}

モジュールメモ

Jcode.pm

特に断らなければ Jcode.pm 0.75 NoXS バージョンの話です。

入力する文字の符号化方式

jcode($_)->sjis; なんてのは普通に使いますが、これだとまず $_ の文字符号化方式が何なのかを判別してから sjis に変換します。文字符号化方式が分かっているなら、jcode($_, 'euc')->sjis; などとした方が圧倒的に速く、文字化けもしなくなります。

Jcode::mime_encode

folding 処理もちゃんとやってくれる優れものなのではありますが……。

use strict;
use Jcode;

my $name =
 q{じゅげむじゅげむごこうのすりきれかいじゃりすいぎょのすいぎょうまつうん}
.q{らいまつふうらいまつくうねるところにすむところやぶらこうじのぶらこうじ}
.q{ぱいぽぱいぽぱいぽのしゅーりんがんしゅーりんがんのぐーりんだいぐーりん}
.q{だいのぽんぽこぴーのぽんぽこなーのちょうきゅうめいのちょうすけ};
my $mail = "$name<foo\@example.com>";
my $from = "From: $mail";
my $enc = jcode($from)->mime_encode;
my $dec = jcode($enc)->mime_decode->sjis;

print "$enc\n";
print "$dec\n";

問題は、mime_encode しなくて良い部分まで encode されてしまうということ。先頭の "From:" は残るのですが、末尾の "<foo\@example.com>" は encode されてしまいます。From: フィールドにメールアドレスらしいものが無いと判断して勝手に補ってしまう MTA があったりするので激しく残念な思いをします。

ではメールアドレス部分を後で追加すれば良いかというと、それはそれで folding 処理が旨くないわけです。で、妥協案。

use Jcode;
my $name =
 q{じゅげむじゅげむごこうのすりきれかいじゃりすいぎょのすいぎょうまつうん}
.q{らいまつふうらいまつくうねるところにすむところやぶらこうじのぶらこうじ}
.q{ぱいぽぱいぽぱいぽのしゅーりんがんしゅーりんがんのぐーりんだいぐーりん}
.q{だいのぽんぽこぴーのぽんぽこなーのちょうきゅうめいのちょうすけ};
my $mail = $name;
my $enc = jcode($mail)->mime_encode . "\n " . '<foo@example.com>';
my $dec = jcode($enc)->mime_decode->sjis;

print "$enc\n";
print "$dec\n";

まめに folding してはいけないというわけでもありませんから。

Jcode.pm 0.76 の mime_encode

0.76 で mime_encode のバグが直ったらしいので、上記のメールアドレス部分がエンコードされなくなったのかと思ったのですが……。

use strict;
use Jcode 0.76;

my $name = 'てすと';
my $mail = "$name";
my $from = "From: $mail";
my $enc = jcode($from)->mime_encode;
my $dec = jcode($enc)->mime_decode->sjis;

print "$enc\n";
print "$dec\n";

実行結果は以下のとおり。

From: =?ISO-2022-JP?B?GyRCJEYkOSRIGyhCPGZvb0BleGFtcGxlLmNvbT4=?=
From: てすと<foo@example.com>

変わってない……。直ったのは別のところでしたか。

正規表現

後方参照の罠

ある文字列の中の () で括られている部分を別のセルに入れたいと思って、以下のように書いてみたとします。

my @data = qw{えび かに(ずわいがに) くらげ(ホイミスライム)};
foreach my $data (@data){
 $data =~ s/(\(.+\))//g;
 my $sub = $1;
 print qq{<tr><th scope="row">$data</th><td>$sub<td></tr>\n};
}

これは一見問題なく動きますが、実は大きな落とし穴があります。

my @data = qw{えび(さくらえび) かに(ずわいがに) くらげ};
foreach my $data (@data){
 $data =~ s/(\(.+\))//g;
 my $sub = $1;
 print qq{<tr><th scope="row">$data</th><td>$sub<td></tr>\n};
}

こうすれば安心。

my @data = qw{えび(さくらえび) かに(ずわいがに) くらげ};
foreach my $data (@data){
 $data =~ s/(\(.+\))//g and my $sub = $1;
 print qq{<tr><th scope="row">$data</th><td>$sub<td></tr>\n};
}

昭和を西暦に変換する正規表現

どうということは無いのですが、けっこう綺麗だと思ったので。64以上や負の数のときのことは知りません。

$y =~ s/昭和(\d+)/$1+1925/e;

メールアドレスの正規表現

大崎さんの Perl メモメールアドレスの正規表現というのが出ています。これは便利なのですが、若干不満もあります。

そんなわけで、若干手を加えたのがこちら。

$esc         = '\\\\';
$Period      = '\.';
$space       = '\040';
$OpenBR      = '\[';
$CloseBR     = '\]';
$NonASCII    = '\x80-\xff';
$ctrl        = '\000-\037';
$CRlist      = '\n\015';
$qtext       = qq/[^$esc$NonASCII$CRlist\"]/;
$dtext       = qq/[^$esc$NonASCII$CRlist$OpenBR$CloseBR]/;
$quoted_pair = qq<${esc}[^$NonASCII]>;
$atom_char   = qq/[^($space)<>\@,;:\".$esc$OpenBR$CloseBR$ctrl$NonASCII]/;
$atom        = qq<$atom_char+(?!$atom_char)>;
$quoted_str  = qq<\"$qtext*(?:$quoted_pair$qtext*)*\">;
$word        = qq<(?:$atom|$quoted_str)>;
$domain_ref  = $atom;
$domain_lit  = qq<$OpenBR(?:$dtext|$quoted_pair)*$CloseBR>;
$sub_domain  = qq<(?:$domain_ref|$domain_lit)>;
$domain      = qq<$sub_domain(?:$Period$sub_domain)+>;
$local_part  = qq<$word(?:$Period$word)*(?:$Period)?>;
$addr_spec   = qq<$local_part\@$domain>;
$mail_regex  = $addr_spec;

結果、得られたのは以下の文字列です。

(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|"[^\\\x80-\xff\n\015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*")(?:\.(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|"[^\\\x80-\xff\n\015"]*(?:\\[^\x80-\xff][^\\\x80-\xff\n\015"]*)*"))*(?:\.)?@(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff])*\])(?:\.(?:[^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff]+(?![^(\040)<>@,;:".\\\[\]\000-\037\x80-\xff])|\[(?:[^\\\x80-\xff\n\015\[\]]|\\[^\x80-\xff])*\]))+


HTML鳩丸倶楽部

bakera.jp

水無月ばけら, MINAZUKI Bakera
E-mail: bakera@star.email.ne.jp