@kyanny's blog

My thoughts, my life. Views/opinions are my own.

正規表現でマッチした部分のキャプチャ ($1,$2とか) について勘違いをしていた

今後また間違えそうなので丁寧めにメモ。

追記

コメント欄でせいきひょうげんの先読みという機能について教えてもらった。まだ理解できてないけど、たぶんそれを使えばスマートに解決できるのだと思う。あと、例としてかいた式が間違っていたので修正した。

      • -
my ($a, $b) = "/b/x/2" =~ m{^(?:/(a)/x/(1))|(?:/(b)/x/(2))};

のようなコードを書いて、 $a も $b も undef になるのでしばらく悩んでいたけど、この書き方だと問題があった。

以下のようなコードを書いてみたら、意味がわかった。

#!/usr/bin/perl
use strict;
use Data::Dumper;

my $a = q{/a/x/1};
my $b = q{/b/x/2};
my $c = q{/c/x/3};

for my $x ($a, $b, $c) {
    if (my @m = $x =~ m{^(?:/(a)/x/(1))|(?:(b)/x/(2))|(?:(c)/x/(3))}) {
        warn Dumper \@m;
    }
}

() でキャプチャされたマッチングの結果の「位置」は | (OR) があっても関係なくて、左から () の順番どおりに $1, $2, $3, ... と続いていく。だから上の正規表現だと、 a にマッチしたら $1, 1 にマッチしたら $2, b にマッチしたら $3 ... というふうにキャプチャされる。

a と 1 にマッチしなかった場合は $1, $2 は undef になる(マッチしなかったのだから当然といえる)が、左辺値を my ($a, $b) のようにしてしまうと $1, $2 を受け取ってしまうので、たとえ b にマッチして $3 がセットされていてもそれを受け取れない。上の書き直したコードのように、配列で受け取ってみると「どの位置でマッチした文字列が何番目にセットされてるか」がよくわかる。

今回のケースでは、文字列のパターンは同じで /(.*?)/x/(.*?) という風に書けるが、 a と 1 は必ずペアでマッチしなくてはならない、というような縛りがある(/a/x/2 にはマッチしてはいけない)ので横着な書き方をしてはまってしまった。こういう場合は配列で受け取って undef ではない部分だけ grep { defined } @m などとして取り出すか、

if ($x =~ m{^(?:/a/x/1)|(?:b/x/2)|(?:c/x/3)}) {
    my ($a, $b) = $x =~ m{^/(.*?)/x/(.*?)};    
}

のようにして、パターンマッチングと値のキャプチャで二回に分けると難なく取り出せる。