Perl Hackers Hub

第58回 正規表現の勘所―わかりづらい記法の覚え方,先読みや後読みの実践(2)

この記事を読むのに必要な時間:およそ 4 分

2つの修飾子/mと/sの覚え方

2つの修飾子/m/sを紹介しましたが,すでにこれらを学んだことがあっても,ややこしさを感じる人が多いのではないでしょうか。

複数行モードと単一行モード。その名前から対比される概念かと思えば,そもそも双方が意味変更の対象とするメタ文字が違います。

筆者も勉強したてのころはなかなか覚えられず,⁠英語だと自然なのだろうか」と思ったりもしました。しかし,正規表現の名著『詳説 正規表現 第3版』注2でさえ,⁠不適切な名前」⁠p.108⁠⁠,⁠始末が悪い」⁠p.109)と書く有様。英語圏でもややこしさは同じようです。

そうであれば別の覚え方をすればよいのではと,筆者は次の語呂合わせで覚えています。

/mはmultiple match mountain mode

^$が冒頭や末尾だけでなく改行の前や後にもマッチする/mは,multiple match mountain modeと覚えます。山の形の記号^が複数ヵ所にマッチする端的な説明であり,覚え方に頭文字mが何度も登場することで,/mの意味が記憶に刻まれることを期待しています。

$については上記のフレーズ内では言及されませんが,^が複数ヵ所にマッチするのであれば$も同様に連想できるでしょう。

/sはsuper dot mode

メタ文字.が改行にもマッチする/sは,super dot modeと覚えます。今までの.「任意の1文字にマッチする」とされつつも改行にはマッチしない性質がありますが,/sを指定すると.がマッチする文字対象がスーパーになる注3と連想できるでしょう。

注2)
Jeffrey E.F. Friedl著,株式会社ロングテール/長尾高弘訳『詳説 正規表現 第3版』オライリー・ジャパン,2008年
注3)
英和辞書を引くと,superには「最高の」⁠特大の」などの意味があるようです。

/r修飾子─⁠─非破壊で置換を行う

前項までで見てきたとおり,通常の置換演算子s///は置換対象文字列を破壊します。破壊するとは,変数の内容を再代入することなく変更することを言います。

本項で解説する/r修飾子の登場以前の古いPerlで破壊を避けたい場合,代入で文字列のコピーを作成し,それに対して置換を実行していました。

my $input = "アリスさんとボブくん";  ――(1)
my $output = $input;  ――(2)
$output =~ s/君|くん|さん/さま/g;  ――(3)
print "$input → $output\n";

(1)で定義した文字列を破壊することなく置換結果を得たい場合,(2)で代入によって文字列のコピーを別の変数に確保したうえで,(3)でコピーを確保した変数に対して置換を行います。(2)でコピーを確保せず(3)$inputに対して置換を行うと,(1)の内容が失われます。

しかし,上記手順は煩雑なこともあり,Perl 5.14で置換を非破壊で行う/r修飾子が登場しました。

my $input = "アリスさんとボブくん";
my $output = $input =~ s/君|くん|さん/さま/gr;  ――(1)
print "$input → $output\n";

(1)/r修飾子を指定することによって,$input自体は破壊されず,$outputに置換結果の文字列が代入されます。

別名変数

置換演算子s///が破壊的であることで特に不都合な場合があるのは,formapの一時変数です。

my @names = ("Alice", "Bob", "Carol", "Dave");
print "ループ内: ";
for my $name (@names) {
    $name =~ s/^(.).*(.)$/$1...$2/;  ――(1)
    print "$name ";
}
print "\n元の配列: @names\n";  ――(2)

(1)では,"Alice""A...e"となるよう,名前を一部伏せる置換を施しています。しかし,forの一時変数$nameを破壊しただけだと思ったら,(2)で元の配列も破壊されていることがわかります。

ループ内: A...e B...b C...l D...e
元の配列: A...e B...b C...l D...e

この$nameのような変数は別名変数と呼ばれ,繰り返しの配列の特定の要素表記$names[0]などの,文字どおり別名となります。つまり,たとえば繰り返しの初回であれば,$name$names[0]双方を評価した値は同一,一方を破壊すると他方も破壊されます注4⁠。

forやmapと相性が良いs///r

前項のような場合,変数を破壊しない/r修飾子が有用です。前項の例のfor文を書き換えると以下になります。

for my $name (@names) {
    print $name =~ s/^(.).*(.)$/$1...$2/r, "\n";
}

特にmapは,一時変数が$_固定であることと,s///=~を伴わない場合の置換対象が$_となるため,無意識に元の配列を破壊する場合があります。

my @names = ("Alice", "Bob", "Carol", "Dave");
my @snips = map { s/^(.).*(.)$/$1...$2/; $_ } @names;
print "@snips\n";
print "@names\n"; # 破壊されている!

/r修飾子によって,mapの中で$_を破壊せず,自然に書くことができます。

my @names = ("Alice", "Bob", "Carol", "Dave");
my @snips = map { s/^(.).*(.)$/$1...$2/r } @names;
print "@snips\n";
print "@names\n"; # 元のデータは無事

/r修飾子を使うことができない場合,mapのブロック内にて$_のコピーを作成することで対応します。

my @snips = map {
    my $x = $_; $x =~ s/^(.).*(.)$/$1...$2/; $x
} @names;
注4)
リファレンスと似ていますが,違う概念です。なお,Perl 5.22で登場したrefaliasingを使うと,別名変数を独自に作成できます。詳しくは,perldoc featureを参照してください。

<続きの(3)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.116

2020年4月24日発売
B5判/152ページ
定価(本体1,480円+税)
ISBN978-4-297-11345-2

  • 特集1
    はじめてのトラブルシューティング
  • 特集2
    [実践]AWS CodeDeploy
  • 特集3
    アプリケーションアクセシビリティ

著者プロフィール

尾形鉄次(おがたてつじ)

大学院卒業後の2003年,Webメール開発会社に入社。以後10年以上,Perlを使ったWeb開発に携わる。

2015年,株式会社ガイアックスに入社。オンプレやクラウドの構築や保守運用を担当するインフラチームに所属しつつ,2018年頃から社外イベント運営やプログラミング教育などにも携わっている。

教育に関しては大学でのTAの経験などから課題感を持っていたが,2013年以降にPerl入学式などのコミュニティで教える側に立つことにつながり,そこで得られた知見を社内外で活かしている。

2019年よりJapan Perl Association理事,およびPerl入学式2代目代表を務める。