Perl中的Regex Group:如何从正则表达式组中将元素捕获到数组中,以匹配字符串中未知数量的/多个/变量出现?

Regex Group in Perl: how to capture elements into array from regex group that matches unknown number of/multiple/variable occurrences from a string?

在Perl中,如何使用一个regex分组将与之匹配的多个事件捕获到多个数组元素中?

例如,对于字符串:

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

要用代码处理此问题:

1
2
3
4
5
6
7
8
9
$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]."
"
;
}

我希望看到输出:

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

我会用什么做正则表达式?

这里我想要匹配的东西之间的共性是一个赋值字符串模式,因此类似于:

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

其中*表示与组匹配的一个或多个事件。

(我不考虑使用split(),因为某些匹配项本身包含空格(即var3…),因此不会给出所需的结果。)

有了上面的regex,我只得到:

1
0: var1=100 var2

在正则表达式中是可能的吗?或者需要附加代码?

在搜索"perl-regex多组"时,已经查看了现有的答案,但没有足够的线索:

  • 处理多个记录中的多个捕获组
  • 一个regex组中有多个匹配项?
  • regex:重复捕获组
  • 正则表达式匹配和分组
  • 如何与组数未知的分组匹配
  • awk从每行提取多个组
  • 匹配多个regex组并删除它们
  • Perl:删除满足某个标准的多个重新固化行
  • 每行匹配多个组的regex?
  • php regex分组多个匹配项
  • 如何使用regex组查找多个匹配项?


1
2
3
4
5
6
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>
"
;
}

印刷品:

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

说明:

最后一项:末尾的g标志意味着您可以多次将regex应用于字符串。第二次将继续匹配最后一个匹配在字符串中结束的位置。

现在对于regex:(?:^|\s+)匹配字符串的开头或一组一个或多个空格。这是必需的,所以下次应用regex时,我们将跳过键/值对之间的空格。?:表示括号内容不会被作为组捕获(我们不需要空格,只需要键和值)。\S+与变量名匹配。然后我们跳过任意数量的空格和中间的等号。最后,("[^"]*"|\S*)/将两个引号与中间任意数量的字符匹配,或者将任意数量的非空格字符与值匹配。请注意,报价匹配非常脆弱,无法正确处理escpapped报价,例如"\"quoted\""将导致"\"

编辑:

由于您真的想要得到整个分配,而不是单个键/值,这里有一个一行程序来提取这些内容:

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


对于正则表达式,使用一种我喜欢称之为Tack和Stretch的技术:锚定您知道将要出现的特性(Tack),然后抓住介于两者之间的特性(Stretch)。

在这种情况下,您知道单个分配匹配

1
\b\w+=.+

$string中有很多重复的。记住,\b表示单词边界:

A word boundary (\b) is a spot between two characters that has a \w on one side of it and a \W on the other side of it (in either order), counting the imaginary characters off the beginning and end of the string as matching a \W.

赋值中的值用正则表达式来描述可能有点困难,但是您也知道每个值都将以空格结尾,尽管不一定是遇到的第一个空格!-后跟另一个赋值或字符串结尾。

为了避免重复断言模式,使用qr//编译一次,然后在模式中重用它,同时使用前瞻性断言(?=...),以将匹配扩展到足以捕获整个值的程度,同时防止它溢出到下一个变量名中。

与您的模式在列表上下文中与m//g匹配提供了以下行为:

The /g modifier specifies global pattern matching—that is, matching as many times as possible within the string. How it behaves depends on the context. In list context, it returns a list of the substrings matched by any capturing parentheses in the regular expression. If there are no parentheses, it returns a list of all the matched strings, as if there were parentheses around the whole pattern.

模式$assignment使用非贪婪的.+?在未来看到另一个分配或行尾时立即切断值。记住,match返回所有捕获子模式的子字符串,因此look ahead的交替使用非捕获(?:...)。相比之下,qr//包含隐式捕获括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /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]."
"
;
}

输出:

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


我不是说这是你应该做的,但你想做的是写语法。现在,您的示例对于语法来说非常简单,但是Damian Conway的模块regexp::grammar s在这方面非常出色。如果你必须种植这个,你会发现它会让你的生活更容易。我在这里用了很多——它有点像Perl6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 {%/};

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$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'
                      }
                    ]


也许有点过头了,但我可以找个借口看看http://p3rl.org/parse::recscent。做一个解析器怎么样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/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}
"
; }

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);

产量:

1
2
3
4
5
6
7
var1 => 100
var2 => 90
var5 => hello
var3 =>"a, b, c"
var7 => test
var8 =>" haha " heh"
var3 => hello

注意双变量3,如果希望后一个赋值覆盖第一个赋值,可以使用哈希来存储值,然后在以后使用它们。

PPS。我的第一个想法是在"="上拆分,但是如果包含"="的字符串失败了,而且由于regexps几乎总是不利于解析,所以我最终尝试了它,它可以工作。

编辑:添加了对带引号字符串内转义引号的支持。


我最近不得不分析X509证书的"主题"行。它们的形式与您提供的类似:

1
2
3
4
5
6
7
8
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"$_
" foreach @a;'


C=HU
L=Budapest
O=Microsec Ltd.
CN=Microsec e-Szigno Root CA 2009/emailAddress=info@e-szigno.hu

regex的简短描述:

(\w+\=.+?)—捕获单词,后跟"=",以及非贪婪模式下的任何后续符号。(?=(?:, \w+\=|$))—后面跟着另一个, KEY=val或行尾。

使用的regex的有趣部分是:

  • .+?—非贪婪模式
  • (?:pattern)—非捕获模式
  • (?=pattern)零宽度正前瞻断言


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/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;


这一个还将提供双引号的常见转义,例如var3="a",b,c"。

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

行动中:

1
2
3
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


您要求使用regex解决方案或其他代码。这里有一个(大部分)只使用核心模块的非regex解决方案。唯一的regex是用于确定分隔符的\S+;在本例中是一个或多个空格。

1
2
3
4
5
6
7
8
9
10
11
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]."
"
;
}

或者你可以在这里执行代码

输出为:

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

如果你真的想要一个regex的解决方案,艾伦摩尔的评论链接到他的IDeone上的代码是气体!


用正则表达式可以做到这一点,但是它是脆弱的。

1
2
3
4
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;