読者です 読者をやめる 読者になる 読者になる

Perlの正規表現で後ろからマッチさせる

perl

わかってみると単純ながら、けっこうハマったのでメモ。

以下のような文字列があるとして、

my $fruits = '/apple/orange/grape/lemon/banana/';

このうち「lemon」の一つ前にある果物だけを取り出したい、とする。

ただし、上記ではその果物が「grape」であり、その前の果物が「orange」であることが明らかだけど、設問上はそこは不明確というか、可変で、さらに言えばその前の「apple」の前にもまだ他の果物がどんどん入ってくるかもしれない、とする。

つまり確実にわかっているのは、「lemonの一つ前にある果物を取り出したい」ということだけ。

となると、「orange」と「lemon」で対象を挟むことはできないし、前から数えていくつめ、と指定することもできない。
よって「lemon」のほう、後ろからマッチさせる方向で考える。

で、最初はこういうのを書いたんだけど、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+)\/lemon/) {
    say $1;
}

それだと、結果はこうなる。

apple/orange/grape

いわゆる最長マッチ、欲張りなマッチ状態ですね。

んで、じゃあ最短マッチ(非欲張りマッチ)ってどうやるんだっけ、と思ってこのようにやると、

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+?)\/lemon/) {  #<= 「+」の後に「?」を追加
    say $1;
}

実行。

apple/orange/grape

変わらない。そういう問題ではなかったみたい。

ここまで来て、ん〜あれ、正規表現って後ろからマッチしていくこと自体そもそもできないんだっけ・・?? とかしばらく考え込んでしまったのだけど、結論としてはこんな。

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /.+\/(.+)\/lemon/) {
    say $1;
}

実行。

grape

はい。
ということで、パターンの頭に「.+」を付けただけでした。「.*」でも構わない。

またひとつ ぱーる の どうぐ を てにいれた! という感じでした。