Perlで括弧などで囲まれたテキストを一致させたいです。どうやってやるの?
これは公式 perlfaqからの質問です。perlfaq を Stack Overflow にインポートしています。
これは、公式の FAQ の回答からその後の編集を差し引いたものです。
あなたの最初の試みはおそらく、Perl 5.8 以降の Perl 標準ライブラリにあるText::Balancedモジュールであるべきです。トリッキーなテキストを処理するためのさまざまな機能があります。Regexp::Commonモジュールは、使用できる既定のパターンを提供することによっても役立ちます。
Perl 5.10 では、再帰パターンを使用してバランスのとれたテキストを正規表現と一致させることができます。(??{})
Perl 5.10 より前では、Perl コードをシーケンスで使用するなど、さまざまなトリックに頼らなければなりませんでした。
再帰的な正規表現を使用した例を次に示します。目標は、ネストされた山括弧内のテキストを含め、山括弧内のすべてのテキストをキャプチャすることです。このサンプル テキストには、2 つの「主要な」グループがあります。1 レベルのネストを持つグループと 2 レベルのネストを持つグループです。山括弧内には合計 5 つのグループがあります。
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
バランスのとれたテキストに一致する正規表現は、2 つの新しい (Perl 5.10 までの) 正規表現機能を使用します。これらはperlreでカバーされており、この例はそのドキュメントの修正版です。
まず、新しい所有格を量指定子に追加する+
と、最長一致が検出され、後戻りしません。バックトラックではなく、再帰を通じて山かっこを処理する必要があるため、これは重要です。グループ[^<>]++
は、後戻りせずに 1 つ以上の非山かっこを見つけます。
次に、 new(?PARNO)
は で指定された特定のキャプチャ グループのサブパターンを参照しPARNO
ます。次の正規表現では、最初のキャプチャ グループがバランスの取れたテキストを検出 (および記憶) します。ネストされたテキストを通過するには、最初のバッファー内に同じパターンが必要です。それが再帰的な部分です。は(?1)
、外側のキャプチャ グループのパターンを正規表現の独立した部分として使用します。
すべてをまとめると、次のようになります。
#!/usr/local/bin/perl5.10.0
my $string =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my @groups = $string =~ m/
( # start of capture group 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # found < or >, so recurse to capture group 1
)*
> # match a closing angle bracket
) # end of capture group 1
/xg;
$" = "\n\t";
print "Found:\n\t@groups\n";
出力は、Perl が 2 つの主要なグループを見つけたことを示しています。
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > >
少し余分な作業を行うと、他の山かっこ内にある場合でも、山かっこ内のすべてのグループを取得できます。バランスの取れた一致を取得するたびに、その外側の区切り文字 (これは一致したばかりなので、再度一致させないでください) を削除し、処理する文字列のキューに追加します。一致するものがなくなるまで、それを続けます。
#!/usr/local/bin/perl5.10.0
my @queue =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my $regex = qr/
( # start of bracket 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # recurse to bracket 1
)*
> # match a closing angle bracket
) # end of bracket 1
/x;
$" = "\n\t";
while( @queue )
{
my $string = shift @queue;
my @groups = $string =~ m/$regex/g;
print "Found:\n\t@groups\n\n" if @groups;
unshift @queue, map { s/^<//; s/>$//; $_ } @groups;
}
出力にはすべてのグループが表示されます。最も外側の一致が最初に表示され、ネストされた一致が後で表示されます。
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > >
Found:
<nested brackets>
Found:
<nested once <nested twice> >
Found:
<nested twice>