11

{}ファイルから特定のテキストを抽出できるように、ネストされた角かっことをPerlの正規表現と一致させようとしています。これは私が現在持っているものです:

my @matches = $str =~ /\{(?:\{.*\}|[^\{])*\}|\w+/sg;

foreach (@matches) {
    print "$_\n";
}

場合によっては、これは期待どおりに機能します。たとえば、次のよう$str = "abc {{xyz} abc} {xyz}"に取得した場合:

abc
{{xyz} abc}
{xyz}

予想通り。ただし、他の入力文字列の場合、期待どおりに機能しません。たとえば、の場合$str = "{abc} {{xyz}} abc"、出力は次のようになります。

{abc} {{xyz}}
abc

それは私が期待したものではありません。それぞれが括弧の点で独自にバランスが取れているので、私は別々の行になりたい{abc}と思っていたでしょう。{{xyz}}正規表現に問題はありますか?もしそうなら、私はそれをどのように修正しますか?

4

7 に答える 7

18

パターンが一致したことに驚きましたが、誰も説明しませんでしたか? パターンの一致方法は次のとおりです。

my @matches = $str =~ /\{(?:\{.*\}|[^{])*\}|\w+/sg;
                       ^    ^ ^ ^  ^      ^
                       |    | | |  |      |
{ ---------------------+    | | |  |      |
a --------------------------)-)-)--+      |
b --------------------------)-)-)--+      |
c --------------------------)-)-)--+      |
} --------------------------)-)-)--+      |
  --------------------------)-)-)--+      |
{ --------------------------+ | |         |
{ ----------------------------+ |         |
x ----------------------------+ |         |
y ----------------------------+ |         |
z ----------------------------+ |         |
} ------------------------------+         |
} ----------------------------------------+

ご覧のとおり、問題は / \{.*\}/ が一致しすぎることです。そこにあるべきものは一致するものです

(?: \s* (?: \{ ... \} | \w+ ) )*

どこに...ある

(?: \s* (?: \{ ... \} | \w+ ) )*

したがって、再帰が必要です。名前付きグループは、これを行う簡単な方法です。

say $1
   while /
      \G \s*+ ( (?&WORD) | (?&BRACKETED) )

      (?(DEFINE)
         (?<WORD>      \s* \w+ )
         (?<BRACKETED> \s* \{ (?&TEXT)? \s* \} )
         (?<TEXT>      (?: (?&WORD) | (?&BRACKETED) )+ )
      )
   /xg;

しかし、車輪を再発明する代わりに、Text::Balancedを使用しない理由はありません。

于 2013-03-08T20:57:49.310 に答える
14

バランスの取れたネストされた区切り文字を一致させる問題はperlfaq5でカバーされており、 (?PARNO)Regexp::Commonを含むすべてのオプションをカバーするためにそれらに任せます。

しかし、バランスのとれたアイテムのマッチングはトリッキーでエラーが発生しやすいです。高度な正規表現を本当に学習して維持したい場合を除き、モジュールに任せてください。幸いなことに、これを処理するためのText::Balancedがあります。バランスの取れたテキスト マッチングのスイス アーミー チェーンソーです。

残念ながら、括弧で囲まれた区切り文字のエスケープを処理しません

use v5.10;
use strict;
use warnings;

use Text::Balanced qw(extract_multiple extract_bracketed);

my @strings = ("abc {{xyz} abc} {xyz}", "{abc} {{xyz}} abc");

for my $string (@strings) {
    say "Extracting from $string";

    # Extract all the fields, rather than one at a time.
    my @fields = extract_multiple(
        $string,
        [
            # Extract {...}
            sub { extract_bracketed($_[0], '{}') },
            # Also extract any other non whitespace
            qr/\S+/
        ],
        # Return all the fields
        undef,
        # Throw out anything which does not match
        1
    );

    say join "\n", @fields;
    print "\n";
}

extract_multipleは、より一般的で強力なsplitのように考えることができます。

于 2013-03-08T20:01:17.783 に答える
6

ネストされたブラケットを、ネストの各レベルで 1 つのペアだけに一致さ
せるには、任意の数のレベルに一致させます。たとえば{1{2{3}}}、次のように使用できます。

/\{[^}]*[^{]*\}|\w+/g

ネストの任意のレベルで複数のペアが存在する可能性がある場合に一致させるには、たとえば{1{2}{2}{2}}、次を使用できます

/(?>\{(?:[^{}]*|(?R))*\})|\w+/g

(?R)、パターン全体を再帰的に一致させるために使用されます。

括弧のペア内に含まれるテキストを一致させるには、エンジンが一致する必要があります(?:[^{}]*|(?R))*
つまり、 or のいずれ[^{}]*(?R)が 0 回以上一致する必要があります*

たとえば"{abc {def}}"では、オープニング"{"が一致した後、 が[^{}]*に一致し"abc "(?R)が に一致し"{def}"、次にクロージング"}"が一致します。

"{def}"が一致するの(?R)は、 がパターン全体の単純な短縮形であるためです
(?>\{(?:[^{}]*|(?R))*\})|\w+。これは、先ほど見たように、 a に"{"続いてテキスト マッチングに一致[^{}]*し、その後に"}".

アトミック グループ化(?>...)は、一致した後に正規表現エンジンが括弧で囲まれたテキストにバックトラックするのを防ぐために使用されます。これは、一致が見つからない場合に正規表現がすぐに失敗するようにするために重要です。

于 2013-03-08T19:39:20.373 に答える
5

再帰的な正規表現が必要です。これは機能するはずです:

my @matches;
push @matches, $1 while $str =~ /( [^{}\s]+ | ( \{ (?: [^{}]+ | (?2) )* \} ) )/xg;

または、非ループバージョンを好む場合:

my @matches = $str =~ /[^{}\s]+ | \{ (?: (?R) | [^{}]+ )+ \} /gx;
于 2013-03-08T19:39:04.877 に答える
4

わお。単純なものに対する複雑な答えの束。

あなたが抱えている問題は、貪欲なモードでマッチングしているということです。つまり、式を真にしながら、可能な限り一致するように正規表現エンジンを要求しています。

貪欲な一致を回避するには、「?」を追加するだけです。数量詞の後。それは試合をできるだけ短くします。

だから、私はあなたの表現を以下から変更しました:

my @matches = $str =~ /\{(?:\{.*\}|[^\{])*\}|\w+/sg;

に:

my @matches = $str =~ /\{(?:\{.*?\}|[^\{])*?\}|\w+/sg;

...そして今、それはあなたが期待している通りに正確に機能します。

HTH

フランシスコ

于 2013-03-15T13:15:39.517 に答える
2

組み込みモジュールを使用する 1 つの方法Text::Balanced

の内容script.pl:

#!/usr/bin/env perl

use warnings;
use strict;
use Text::Balanced qw<extract_bracketed>;

while ( <DATA> ) { 

    ## Remove '\n' from input string.
    chomp;

    printf qq|%s\n|, $_; 
    print "=" x 20, "\n";


    ## Extract all characters just before first curly bracket.
    my @str_parts = extract_bracketed( $_, '{}', '[^{}]*' );

    if ( $str_parts[2] ) { 
        printf qq|%s\n|, $str_parts[2];
    }   

    my $str_without_prefix = "@str_parts[0,1]";


    ## Extract data of balanced curly brackets, remove leading and trailing
    ## spaces and print.
    while ( my $match = extract_bracketed( $str_without_prefix, '{}' ) ) { 
        $match =~ s/^\s+//;
        $match =~ s/\s+$//;
        printf qq|%s\n|, $match;

    }   

    print "\n";
}

__DATA__
abc {{xyz} abc} {xyz}
{abc} {{xyz}} abc

次のように実行します。

perl script.pl

これにより、次の結果が得られます。

abc {{xyz} abc} {xyz}
====================
abc 
{{xyz} abc}
{xyz}

{abc} {{xyz}} abc
====================
{abc}
{{xyz}}
于 2013-03-08T20:00:07.517 に答える
1

従来のソリューションを少し変更して拡張するだけです。

(\{(?:(?1)|[^{}]*+)++\})|[^{}\s]++

デモ(これは PCRE にあります。再帰正規表現に関しては動作が Perl とは少し異なりますが、この場合は同じ結果になるはずです)。

少し苦労した後 (私は Perl に詳しくありません!)、これはideoneのデモです。$&正規表現全体に一致する文字列を参照します。

my $str = "abc {{xyz} abc} {xyz} {abc} {{xyz}} abc";

while ($str =~ /(\{(?:(?1)|[^{}]*+)++\})|[^{}\s]++/g) {
    print "$&\n"
}

このソリューションは、入力が有効であることを前提としていることに注意してください。無効な入力ではかなりランダムに動作します。無効な入力が検出されたときに停止するように少し変更できます。abc{xyz}asdそのためには、有効な入力と見なされるかどうかなど、入力形式 (できれば文法) に関する詳細が必要です。

于 2013-03-08T19:35:18.037 に答える