web-dev-qa-db-ja.com

Perlの正規表現グループ:文字列からの未知数/複数/変数の出現に一致する正規表現グループから配列に要素をキャプチャする方法

Perlでは、1つの正規表現グループを使用して、それに一致する複数のオカレンスを複数の配列要素にキャプチャするにはどうすればよいですか?

たとえば、文字列の場合:

var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello

コードでこれを処理するには:

$string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my @array = $string =~ <regular expression here>

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
  print $i.": ".$array[$i]."\n";
}

出力として見たい:

0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello

正規表現として何を使用しますか?

ここで一致させたいものの共通点は、割り当て文字列パターンです。したがって、次のようなものです。

my @array = $string =~ m/(\w+=[\w\"\,\s]+)*/;

*は、グループに一致する1つ以上のオカレンスを示します。

(一部の一致にはスペースが含まれているため(var3 ...)、必要な結果が得られないため、split()を使用して割引しました。)

上記の正規表現では、次のもののみが取得されます。

0: var1=100 var2

正規表現で可能ですか?または追加コードが必要ですか?

「Perl regex multiple group」を検索する際に、既存の回答をすでに見ましたが、十分な手がかりはありません。

47
therobyouknow
my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

while($string =~ /(?:^|\s+)(\S+)\s*=\s*("[^"]*"|\S*)/g) {
        print "<$1> => <$2>\n";
}

プリント:

<var1> => <100>
<var2> => <90>
<var5> => <hello>
<var3> => <"a, b, c">
<var7> => <test>
<var3> => <hello>

説明:

最初の最後の部分:最後のgフラグは、文字列に正規表現を複数回適用できることを意味します。 2回目は、文字列の最後の一致が終了した場所で一致を継続します。

正規表現の場合:(?:^|\s+)は、文字列の先頭または1つ以上のスペースのグループに一致します。これが必要なのは、次回正規表現が適用されるときに、キーと値のペアの間のスペースをスキップするためです。 ?:は、括弧のコンテンツがグループとしてキャプチャされないことを意味します(スペースは不要で、キーと値のみが必要です)。 \S+は変数名と一致します。次に、任意の量のスペースとその間の等号をスキップします。最後に、("[^"]*"|\S*)/は、値の間に任意の量の文字を含む2つの引用符、または任意の量の非スペース文字のいずれかに一致します。引用符のマッチングは非常に壊れやすく、escpapedの引用符を適切に処理しないことに注意してください。 "\"quoted\"""\"になります。

編集:

あなたは本当に単一のキー/値ではなく、割り当て全体を取得したいので、それらを抽出するワンライナーがあります:

my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
42
jkramer

正規表現では、タックアンドストレッチと呼ばれる手法を使用します。つまり、そこにあることがわかっている機能にアンカーを付け(タック)、次にその間にあるものをつかみます(ストレッチ)。

この場合、単一の割り当てが一致することがわかります

_\b\w+=.+
_

これらの多くは_$string_で繰り返されます。 _\b_はワード境界を意味することに注意してください:

単語の境界(_\b_)は、片側に_\w_があり、反対側に_\W_がある(どちらかの順序で)2つの文字の間のスポットです。 _\W_に一致するものとして、文字列の先頭と末尾にある想像上の文字。

割り当ての値は、正規表現で説明するのが少し難しい場合がありますが、各値が空白で終わることも知っています(必ずしも最初の空白が検出されるとは限りません!)。その後に別の割り当てまたは文字列の終わりが続きます。

アサーションパターンの繰り返しを回避するには、 _qr//_ で1回コンパイルし、 look-aheadアサーション_(?=...)_ は、値全体をキャプチャするだけでなく、次の変数名にこぼれないように一致を拡大します。

_m//g_を使用してリストコンテキストのパターンと一致すると、次の動作が行われます。

_/g_修飾子は、グローバルパターンマッチング、つまり、文字列内で可能な限り多くのパターンマッチングを指定します。動作はコンテキストによって異なります。リストコンテキストでは、正規表現のキャプチャかっこで一致した部分文字列のリストを返します。括弧がない場合は、パターン全体に括弧があるかのように、一致したすべての文字列のリストを返します。

パターン_$assignment_は、先読みが別の割り当てまたは行末を検出するとすぐに、貪欲でない_.+?_を使用して値を切り捨てます。一致はallキャプチャサブパターンからサブストリングを返すため、先読みの代替では非キャプチャ_(?:...)_が使用されることに注意してください。対照的に、_qr//_には暗黙的なキャプチャ括弧が含まれています。

_#! /usr/bin/Perl

use warnings;
use strict;

my $string = <<'EOF';
var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello
EOF

my $assignment = qr/\b\w+ = .+?/x;
my @array = $string =~ /$assignment (?= \s+ (?: $ | $assignment))/gx;

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
  print $i.": ".$array[$i]."\n";
}
_

出力:

0:var1 = 100 
 1:var2 = 90 
 2:var5 = hello 
 3:var3 = "a、b、c" 
 4:var7 = test 
 5:var3 = hello
8
Greg Bacon

私はこれがあなたがすべきであることを言っているわけではありませんが、あなたがやろうとしているのはGrammar。今、あなたの例は文法のためにveryシンプルですが、 Damian Conway のモジュール Regexp :: Grammars is本当にこれで素晴らしい。これをまったく成長させなければならない場合、それはあなたの人生をはるかに楽にするでしょう。ここではかなり使います-Perl6-ishのようなものです。

use Regexp::Grammars;
use Data::Dumper;
use strict;
use warnings;

my $parser = qr{
    <[pair]>+
    <rule: pair>     <key>=(?:"<list>"|<value=literal>)
    <token: key>     var\d+
    <rule: list>     <[MATCH=literal]> ** (,)
    <token: literal> \S+

}xms;

q[var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello] =~ $parser;
die Dumper {%/};

出力:

$VAR1 = {
          '' => 'var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello',
          'pair' => [
                      {
                        '' => 'var1=100',
                        'value' => '100',
                        'key' => 'var1'
                      },
                      {
                        '' => 'var2=90',
                        'value' => '90',
                        'key' => 'var2'
                      },
                      {
                        '' => 'var5=hello',
                        'value' => 'hello',
                        'key' => 'var5'
                      },
                      {
                        '' => 'var3="a, b, c"',
                        'key' => 'var3',
                        'list' => [
                                    'a',
                                    'b',
                                    'c'
                                  ]
                      },
                      {
                        '' => 'var7=test',
                        'value' => 'test',
                        'key' => 'var7'
                      },
                      {
                        '' => 'var3=hello',
                        'value' => 'hello',
                        'key' => 'var3'
                      }
                    ]
7
Evan Carroll

多分少し上かもしれませんが、 http://p3rl.org/Parse::RecDescent を調べる理由になります。パーサーを作成してみませんか?

#!/usr/bin/Perl

use strict;
use warnings;

use Parse::RecDescent;

use Regexp::Common;

my $grammar = <<'_EOGRAMMAR_'
INTEGER: /[-+]?\d+/
STRING: /\S+/
QSTRING: /$Regexp::Common::RE{quoted}/

VARIABLE: /var\d+/
VALUE: ( QSTRING | STRING | INTEGER )

assignment: VARIABLE "=" VALUE /[\s]*/ { print "$item{VARIABLE} => $item{VALUE}\n"; }

startrule: assignment(s)
_EOGRAMMAR_
;

$Parse::RecDescent::skip = '';
my $parser = Parse::RecDescent->new($grammar);

my $code = q{var1=100 var2=90 var5=hello var3="a, b, c" var7=test var8=" haha \" heh " var3=hello};
$parser->startrule($code);

収量:

var1 => 100
var2 => 90
var5 => hello
var3 => "a, b, c"
var7 => test
var8 => " haha \" heh "
var3 => hello

PS。二重のvar3に注意してください。後者の割り当てで最初の割り当てを上書きする場合は、ハッシュを使用して値を保存し、後で使用できます。

PPS。私の最初の考えは「=」で分割することでしたが、文字列に「=」が含まれていれば失敗し、正規表現はほとんど常に解析に悪いので、うまく試しました。

編集:引用文字列内のエスケープ引用符のサポートを追加しました。

4
nicomen

最近、x509証明書の「Subject」行を解析する必要がありました。それらはあなたが提供したものと同様の形をしていました:

echo 'Subject: C=HU, L=Budapest, O=Microsec Ltd., CN=Microsec e-Szigno Root CA 2009/[email protected]' | \
  Perl -wne 'my @a = m/(\w+\=.+?)(?=(?:, \w+\=|$))/g; print "$_\n" foreach @a;'

C=HU
L=Budapest
O=Microsec Ltd.
CN=Microsec e-Szigno Root CA 2009/[email protected]

正規表現の簡単な説明:

(\w+\=.+?)-単語の後に「=」とそれに続く記号を貪欲でないモードでキャプチャします
(?=(?:, \w+\=|$))-後に別の, KEY=valまたは行末。

使用される正規表現の興味深い部分は次のとおりです。

  • .+?-貪欲でないモード
  • (?:pattern)-非キャプチャモード
  • (?=pattern)ゼロ幅の正の先読みアサーション
3
Delian Krustev

これにより、たとえばvar3 = "a、\" b、c "のように、二重引用符で囲まれた一般的なエスケープも提供されます。

@a = /(\w+=(?:\w+|"(?:[^\\"]*(?:\\.[^\\"]*)*)*"))/g;

動作中:

echo 'var1=100 var2=90 var42="foo\"bar\\" var5=hello var3="a, b, c" var7=test var3=hello' |
Perl -nle '@a = /(\w+=(?:\w+|"(?:[^\\"]*(?:\\.[^\\"]*)*)*"))/g; $,=","; print @a'
var1=100,var2=90,var42="foo\"bar\\",var5=hello,var3="a, b, c",var7=test,var3=hello
#!/usr/bin/Perl

use strict; use warnings;

use Text::ParseWords;
use YAML;

my $string =
    "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my @parts = shellwords $string;
print Dump \@parts;

@parts = map { { split /=/ } } @parts;

print Dump \@parts;
2
Sinan Ünür

RegExソリューションまたは他のコードを要求しました。コアモジュールのみを使用した(ほとんど)正規表現以外のソリューションを次に示します。唯一の正規表現は\s+区切り文字を決定します。この場合、1つ以上のスペース。

use strict; use warnings;
use Text::ParseWords;
my $string="var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";  

my @array = quotewords('\s+', 0, $string);

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
    print $i.": ".$array[$i]."\n";
}

または、コードを実行できます [〜#〜] here [〜#〜]

出力は次のとおりです。

0: var1=100
1: var2=90
2: var5=hello
3: var3=a, b, c
4: var7=test
5: var3=hello

本当に正規表現ソリューションが必要な場合は、Alan Mooreの comment IDEoneでの彼のコードへのリンクがガスです!

1
dawg

正規表現を使用してこれを行うことは可能ですが、脆弱です。

my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my $regexp = qr/( (?:\w+=[\w\,]+) | (?:\w+=\"[^\"]*\") )/x;
my @matches = $string =~ /$regexp/g;
0
szbalint