Perl の正規表現でマッチした文字列自体を取得する今昔


# バージョンごとの表

以下で解説しているそれぞれの手法がどの perl のバージョンに対応しているか、表にまとめてみました。

| 手法 | 5.0 | 5.6〜5.8 | 5.10〜5.14 | 5.16〜5.18 | 5.20〜 | 備考 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| マッチ全体キャプチャ  | :o: | :o: | :o: | :o: | :o: | |
| オフセット配列 `@-` `@+` |  | :o: | :o: | :o: | :o: | 稀な手法 |
| `/p` と `${^MATCH}` |   |   | :o: | :o: | :o: | 5.20以降は `/p` 修飾子不要 |
| `$&`  | :bug: | :bug: | :bug: | :bug: | :o: |

バージョンごとの表の絵文字は以下の意味です

- :o: 使用可能
- :bug: 使用可能だが致命的な性能劣化を招くため忌避される
- （空欄） 対応前バージョン

Perl 5.20 以降なら `$&` による方法が一番簡単ですが、それ以前のバージョンでの経緯を知っているチームメンバーから拒絶反応を受ける可能性もあります。そのプログラムが今後 Perl 5.20 より前のバージョンで実行する必要が一切ないと言えるなら `$&` でも問題ないと思いますが、外部公開するモジュールなどの場合は `use v5.20` 等で明言しないのであれば `$&` の使用は避けておくべきでしょう。

# それぞれの手法

## マッチ全体をキャプチャのカッコでくくって `$1` で参照する方法

今も伝統的に行われている、マッチ全体をキャプチャのカッコでくくる方法です。

```pl
# $body から a か A で始まる単語を取り出して $match に入れる
# \b は単語境界を表すゼロ幅マッチ
my $body = "This is apple.";

$body =~ /(\b[Aa]\w+)/;
my $match = $1;
# または my ($match) = $body =~ /(\b[Aa]\w+)/;
```

カッコによる正規表現キャプチャを書くのが手間ではありますが、伝統的で多くのプログラマーにとっておなじみの方法です。

## マッチ先頭オフセット配列 `@-` とマッチ末尾オフセット配列 `@+` による方法

Perl 5.6 で導入された2個の特殊配列 `@-` と `@+` による方法でも取り出すことができます。

```pl
# $body から a か A で始まる単語を取り出して $match に入れる
# \b は単語境界を表すゼロ幅マッチ
my $body = "This is apple.";

$body =~ /(\b[Aa]\w+)/;
my $match = substr($body, $-[0], $+[0]-$-[0]);
# または my ($match) = $body =~ /(\b[Aa]\w+)/;

```

これは [perlvar](https://perldoc.jp/docs/perl/5.20.1/perlvar.pod#:~:text=Perl%205.6.0%20%E3%81%A7%E3%81%AF%E3%80%81%E3%83%9E%E3%83%83%E3%83%81%E3%83%B3%E3%82%B0%E3%81%AB%E6%88%90%E5%8A%9F%E3%81%97%E3%81%9F%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%8F%90%E4%BE%9B%E3%81%99%E3%82%8B%20@-%20%E3%81%A8%20@+%20%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97%E3%81%8C%E5%B0%8E%E5%85%A5%E3%81%95%E3%82%8C%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82) にも書かれた公式の方法ではありますが、一見して分かりづらいことから、通常書かれることは無いでしょう。Perl のバージョンが太古の 5.8 で取れる作戦が限られるけれど、パフォーマンス上の理由により後述の  `$&` は避けるといった限定的な場合にのみ取られる手法だと思います。

## `${^MATCH}` 変数による方法

Perl 5.10 以降には `${^MATCH}` という変数があって、`m//p` 修飾子とともに使うことでマッチ全体を `${^MATCH}` に入れることが出来ます。

```pl
use v5.10;
my $body = "This is apple.";
$body =~ /\b[Aa]\w+/p; # /p 修飾子
my $match = ${^MATCH};
```

その他にも `${^PREMATCH}` と `${^POSTMATCH}` というものもあります。マッチ対象文字列の中で、マッチ全体の左側にあるのが `${^PREMATCH}`、右側にあるのが `${^POSTMATCH}` です。

Perl 5.20 以降は `/p` 修飾子が無くても `${^MATCH}` などに自動的に入ってくれるようになりました。

## [付記] `$&` による方法

実は、いにしえの Perl から、マッチ全体を表す `$&` という変数があったのですが、perl の実装上の問題から、一度でもコードの中で `$&` を書くとプログラム全体の正規表現の性能がガタ落ちになるという致命的な問題がありました。

つまり以前から `${^MATCH}` と同様のアイデアはあったわけですが、実装上の問題から使用が避けられていたのです。

```
${^PREMATCH} == $`

${^MATCH} == $&

${^POSTMATCH} == $'
```

この事実は広く知られることとなり、`$&` は忌避されるようになりました。また、ロードするモジュールにもこれがあってもいけないので、それを探す高度なデバッグテクニックなども開発されることとなりました。

そして Perl 5.10 で `/p` 修飾子が出てくるわけですが、Perl 5.16 で `$&` などの性能向上が図られることとなり、Perl 5.20 ではこの問題が完全に解決され、`$&` などを使うことによる性能低下問題はなくなりました。

また Perl 5.20 以降は `/p` 修飾子は互換性担保のために指定しても無視される修飾子となり、それが指定されていてもされていなくても `${^MATCH}` は定義されているし、また`$&` と `${^MATCH}` は完全に同等になりました。

とはいえ、チーム内で合意が取れているのでなければ `$&` などの変数の使用は現状は避けておいたほうが無難です（もしくは `use v5.20` 指定をするなど）。

詳細は最新バージョンの perl より [`perldoc perlre`](http://perldoc.jp/perlre) を参照ください。

# 参考文献

- [perlvar より正規表現に関する変数](https://perldoc.jp/docs/perl/5.20.1/perlvar.pod#Variables32related32to32regular32expressions) by perldoc.jp