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 |
号
我希望看到输出:
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> |
号
说明:
最后一项:末尾的
现在对于regex:
编辑:
由于您真的想要得到整个分配,而不是单个键/值,这里有一个一行程序来提取这些内容:
1 | my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g; |
对于正则表达式,使用一种我喜欢称之为Tack和Stretch的技术:锚定您知道将要出现的特性(Tack),然后抓住介于两者之间的特性(Stretch)。
在这种情况下,您知道单个分配匹配
1 | \b\w+=.+ |
在
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 .
号
赋值中的值用正则表达式来描述可能有点困难,但是您也知道每个值都将以空格结尾,尽管不一定是遇到的第一个空格!-后跟另一个赋值或字符串结尾。
为了避免重复断言模式,使用
与您的模式在列表上下文中与
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.
号
模式
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的简短描述:
使用的regex的有趣部分是:
.+? —非贪婪模式(?:pattern) —非捕获模式(?=pattern) 零宽度正前瞻断言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
。
这一个还将提供双引号的常见转义,例如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是用于确定分隔符的
1 2 3 4 5 6 7 8 9 10 11 |
号
或者你可以在这里执行代码
输出为:
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; |
。