前回の
正規表現にまつわる修飾子──マッチや置換方法の変更指示
(1)/g修飾子も含む、
| 修飾子 | 意味 | Perlの採用バージョン | 
|---|---|---|
| /m | 複数行モード。^や$のマッチ方法を変える | - | 
| /s | 単一行モード。.が改行にもマッチする | - | 
| /i | 大文字/ | - | 
| /x | 空白を無視したうえでコメントを許可して正規表現を読みやすくする | - | 
| /p | マッチ文字列、 | 5. | 
| /n | 丸括弧()でのキャプチャを抑制する | 5. | 
| /g | マッチを複数回行う | - | 
| /o | 正規表現の結果をキャッシュする | - | 
| /e | 置換文字列をPerlコードとして評価する | - | 
| /r | 置換を非破壊で行う | 5. | 
※採用バージョンに-が入っている場合、
修飾子は複数指定可能で、/msiなどと列挙します。列挙順序は任意で、/imsでも同義です。
以降では、
/m修飾子──冒頭や末尾のメタ文字を、改行文字の直後や直前にもマッチさせる 
/m修飾子は冒頭と末尾のメタ文字^と$のマッチ方法を変えます。この修飾子を指定した状態は複数行モード
/m修飾子を指定しない場合、^と$は対象文字列の冒頭または末尾にマッチします。冒頭も末尾も1ヵ所ですので、/g修飾子を指定しても、
/m修飾子を指定した場合、^と$は対象文字列の冒頭または末尾だけでなく、/g修飾子を合わせて指定すると、
言葉ではわかりづらいので、
my $str1 = <<END_STRING;  ――(1)
  Alice Smith
    Bob Johnson
  Carol Williams
Dave Brown
END_STRING
my $str2 = $str1;  ――(2)
$str1 =~ s/^\s+//g;  ――(3)
print "=== str1 ===\n", $str1;
$str2 =~ s/^\s+//gm;  ――(4)
print "=== str2 ===\n", $str2;$str1に代入しています。$str1のデータを$str2にコピーしたあと、/m指定なしの/m指定ありの^直後にある空白類文字\s+を空文字で置換、
=== str1 ===
Alice Smith
    Bob Johnson
  Carol Williams
Dave Brown
=== str2 ===
Alice Smith
Bob Johnson
Carol Williams
Dave Brown/mなしの場合は文字列の冒頭Aliceの前にある空白のみが削除されますが、/mありの場合は冒頭以外に改行の直後、
末尾のメタ文字$も同様に、/mによって末尾だけでなく行末にもマッチします。
/m修飾子の指定によって、^と$は厳密な冒頭や末尾の意味を失います。その代わり、/m修飾子の有無にかかわらず冒頭にのみマッチする\A、\Zと\zが用意されています/^\s+//をs/\A\s+//に置き換えた場合、Aliceの左側にある空白のみが削除されます。
/s修飾子──ドットを改行文字にもマッチさせる
/s修飾子は、.を改行にもマッチするよう変更します。この修飾子を指定した状態は単一行モード
つまり、/s修飾子を指定しない.は
ドットが標準で改行にマッチしない理由
Perlの誕生以前からあるUNIXコマンドのgrepやsedなどは、.が改行文字にマッチするか否かは未定義だったとも言えます。これらUNIXコマンドでは、.*を指定したとしても、
そのあと誕生したPerlなどの正規表現を備えたプログラミング言語では、.*が行末を越えてマッチすることがないUNIXコマンドユーザーの直感を尊重するため、.は改行にマッチしない方針を取りました。
/s修飾子の有無による違い
しかし、.が改行にマッチしないと不都合であるため、/s修飾子を用います。
実際の例を見てみましょう。
my $str1 = <<END_STRING;  ――(1)
<a href="https://gihyo.jp/dp/">
Gihyo Digital Publishing
</a>
END_STRING
my $str2 = $str1;  ――(2)
if ( $str1 =~ m|<a (.*?)>(.*?)</a>| ) {  ――(3)
    print "str1: リンクを発見しました\n";
    print "属性一覧は $1\n内容は $2\n";
} else{
    print "str1: リンクを発見できませんでした\n";  ――(4)
}
if ( $str2 =~ m|<a (.*?)>(.*?)</a>|s ) {  ――(5)
    print "str2: リンクを発見しました\n";  ┓
    print "属性一覧は $1\n内容は $2\n";    ┛(6)
} else{
    print "str2: リンクを発見できませんでした\n";
}a要素の属性一覧と内容にマッチするサンプルです。a要素の内容に、
/s修飾子がないため、.が改行文字とマッチしません。よって
一方、/s修飾子があるため、.は改行文字にもマッチします。よって
str2: リンクを発見しました
属性一覧は href="https://gihyo.jp/dp/"
内容は
Gihyo Digital Publishing/s修飾子とドットを使用しない方法
メタ文字.が修飾子/sの有無によって意味を変えることが混乱につながるのであれば、■以外の任意の1文字[^ ■ ]を、.の代わりに使う方法もあります。
my $str1 = <<END_STRING;
<a href="https://gihyo.jp/dp/">
Gihyo Digital Publishing
</a>
END_STRING
if ( $str1 =~ m|<a ([^>]*)>([^<]*)</a>| ) {  ――(1)
    print "str1: リンクを発見しました\n";  ┓
    print "属性一覧は $1\n内容は $2\n";    ┛(2)
} else{
    print "str1: リンクを発見できませんでした\n";
}/s修飾子もメタ文字.も使用していないことに注目してください。[^>]と[^<]はそれぞれ>と<以外の任意の1文字を表す正規表現ですが、\nも含まれます。よって、
この[^■]で説明するなら、/s修飾子が指定されていない場合の.は[^\n]と同等です。
マッチを期待する文字列によっては、.を使わずに正規表現を組み立てるほうが、/s修飾子の有無を気にすることなく読みやすい場合があります。
2つの修飾子/mと/sの覚え方
2つの修飾子/mと/sを紹介しましたが、
複数行モードと単一行モード。その名前から対比される概念かと思えば、
筆者も勉強したてのころはなかなか覚えられず、
そうであれば別の覚え方をすればよいのではと、
/mはmultiple match mountain mode
^や$が冒頭や末尾だけでなく改行の前や後にもマッチする/mは、^が複数ヵ所にマッチする端的な説明であり、mが何度も登場することで、/mの意味が記憶に刻まれることを期待しています。
$については上記のフレーズ内では言及されませんが、^が複数ヵ所にマッチするのであれば$も同様に連想できるでしょう。
/sはsuper dot mode
メタ文字.が改行にもマッチする/sは、.は/sを指定すると.がマッチする文字対象がスーパーになる
/r修飾子──非破壊で置換を行う
前項までで見てきたとおり、s///は置換対象文字列を破壊します。破壊するとは、
本項で解説する/r修飾子の登場以前の古いPerlで破壊を避けたい場合、
my $input = "アリスさんとボブくん";  ――(1)
my $output = $input;  ――(2)
$output =~ s/君|くん|さん/さま/g;  ――(3)
print "$input → $output\n";$inputに対して置換を行うと、
しかし、/r修飾子が登場しました。
my $input = "アリスさんとボブくん";
my $output = $input =~ s/君|くん|さん/さま/gr;  ――(1)
print "$input → $output\n";/r修飾子を指定することによって、$input自体は破壊されず、$outputに置換結果の文字列が代入されます。
別名変数
置換演算子s///が破壊的であることで特に不都合な場合があるのは、forやmapの一時変数です。
my @names = ("Alice", "Bob", "Carol", "Dave");
print "ループ内: ";
for my $name (@names) {
    $name =~ s/^(.).*(.)$/$1...$2/;  ――(1)
    print "$name ";
}
print "\n元の配列: @names\n";  ――(2)"Alice"が"A...となるよう、forの一時変数$nameを破壊しただけだと思ったら、
ループ内: A...e B...b C...l D...e
元の配列: A...e B...b C...l D...eこの$nameのような変数は別名変数と呼ばれ、$names[0]などの、$nameと$names[0]双方を評価した値は同一、
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;<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
 イミュータブルデータモデルで始める
 実践データモデリング
 業務の複雑さをシンプルに表現!
- 特集2
 いまはじめるFlutter
 iOS/Android両対応アプリを開発してみよう 
- 特集3
 作って学ぶWeb3
 ブロックチェーン、スマートコントラクト、 NFT 


