鳩丸よもやま話

bakera.jp > 鳩丸よもやま話 > ばけらのPerlメモ

ばけらの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 も同様です。

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";

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

m// の場合、スカラコンテキストではマッチすれば 1, マッチしない場合は '' が返ります。リストコンテキストではマッチした内容の配列が返ります。

my $s = "abcabc";
my @list = $s =~ m/[ab]/g;
my $sc = $s =~ m/[ab]/g;
print "$s\n";
print "$sc\n";
print join(",",@list);

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'};

が、hash の場合は、hash の要素が存在するかどうかを検査する専用の関数 exists が用意されていますので、それを使った方が良いでしょう。defined とは、hash の要素に undef を設定したときの動作が異なってきます。

※exists についてはいわいさん、ZnZ さんからご指摘を頂きました。ありがとうございます。

こんな使い方も……

eval '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; などとした方が圧倒的に速く、文字化けもしなくなります。

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 してはいけないというわけでもありませんから。

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

use strict;
use Jcode 0.76;

my $name = 'てすと';
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";

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

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};
}

$1 は前回マッチした結果を記憶しているので、直前でマッチに失敗していると、空ではなくてその前の結果が格納されているわけです。

こうすれば安心。

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 メモ (www.din.or.jp)メールアドレスの正規表現 (www.din.or.jp)というのが出ています。これは便利なのですが、若干不満もあります。

  • 追記にあるとおり、ローカルパートが . で終わるメールアドレスを弾いてしまいます。「RFC も読んでないようなサーバ管理者を選んだあなたが悪い」と言えば全くそのとおりなのですが、さすがにそれは気の毒です。
  • FQDN でないドメインのメールアドレスを通します。たとえば root@localhost は OK です。このようなメールアドレスはローカルエリアでは通用していることもありますが、インターネットでは通用しません。これは弾きたいという場合がほとんどでしょう。

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

$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])*\]))+

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

……という正規表現を使っていましたが、さらに yuuさんからご指摘を頂きました。

DoCoMo などでは、ローカルパートに . が連続するメールアドレスも使用できるらしいです。もうね……。そんなわけなので、DoCoMo で使用され得る RFC 違反アドレスも一律許容するのであれば、

  • ローカルパートに . が連続するものについても OK とする。たとえば foo...bar....@example.com なんてのを許可します。

という処理も必要です。ぶっちゃけた話、ローカルパートのピリオドについては先頭にさえ出現しなければどこに何度出現しても OK ということになります。これを追加すると以下のようになります。

(?:[^(\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])*\]))+

なんかだんだん正規表現でチェックしている意味が薄れてきているような気がしますが……。まあ必要に応じて使い分けてください。ちなみに、RFC2822 で許容されているメールアドレスのみ許可、かつ root@localhost は不可というパターンは以下のようになります。

(?:[^(\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])*\]))+

最近の日記

関わった本など