Perl 中的正则表达式组:如何从正则表达式组中捕获与字符串中出现的未知数量/多个/变量匹配的元素到数组中?
Posted
技术标签:
【中文标题】Perl 中的正则表达式组:如何从正则表达式组中捕获与字符串中出现的未知数量/多个/变量匹配的元素到数组中?【英文标题】:Regex Group in Perl: how to capture elements into array from regex group that matches unknown number of/multiple/variable occurrences from a string? 【发布时间】:2011-03-28 10:43:05 【问题描述】:在 Perl 中,如何使用一个正则表达式分组将多个匹配它的匹配项捕获到多个数组元素中?
例如,对于一个字符串:
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]+)*/;
其中 * 表示与该组匹配的一个或多个匹配项。
(我使用 split() 打折,因为某些匹配项本身包含空格(即 var3...),因此不会给出预期的结果。)
使用上面的正则表达式,我只得到:
0: var1=100 var2
可以在正则表达式中使用吗?还是需要加码?
在搜索“perl regex multiple group”时已经查看了现有答案,但没有足够的线索:
Dealing with multiple capture groups in multiple records Multiple matches within a regex group? Regex: Repeated capturing groups Regex match and grouping How do I regex match with grouping with unknown number of groups awk extract multiple groups from each line Matching multiple regex groups and removing them Perl: Deleting multiple reccuring lines where a certain criterion is met Regex matching into multiple groups per line? php RegEx Grouping Multiple Matches How to find multiple occurrences with regex groups?【问题讨论】:
TLDR,但 +1 表示你勤奋地做作业。 顺便说一句,我认为您的问题不是多个组,而是匹配的引号。可以在 Perl RegEx 中处理,但要非常小心 ideone.com/Qvm2u @Alan:这是一个很棒的正则表达式! 填补了代码中的空白后,我仍然不确定您的问题涉及到哪一部分。也有点匆忙,我只是发布了链接并保释了。是不是所有匹配项都累积在您试图理解的数组中? 【参考方案1】: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
标志意味着您可以多次将正则表达式应用于字符串。第二次它将继续匹配最后一个匹配在字符串中结束的位置。
现在对于正则表达式:(?:^|\s+)
匹配字符串的开头或一组一个或多个空格。这是必要的,因此当下次应用正则表达式时,我们将跳过键/值对之间的空格。 ?:
表示括号内容不会被捕获为组(我们不需要空格,只需要键和值)。 \S+
匹配变量名。然后我们跳过任意数量的空格和中间的等号。最后,("[^"]*"|\S*)/
匹配两个引号之间任意数量的字符,或任意数量的非空格字符作为值。请注意,引号匹配非常脆弱,无法正确处理转义的引号,例如"\"quoted\""
将导致 "\"
。
编辑:
由于您真的想要获得整个分配,而不是单个键/值,这里有一个提取这些的单行:
my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
【讨论】:
OP 说需要一个正则表达式组,这会捕获到 2 个正则表达式组... 对,我的错。您可以通过在正则表达式的键/值部分周围添加更多括号来解决此问题。 所以你可以这样做:ideone.com/7EQgz :- my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello" ;我的@array = (); while($string =~ /(?:^|\s+)(\S+)\s*=\s*("[^"]*"|\S*)/g) push( @array, $1. "=".$2 ); my @array = (); for ( my $i = 0; $i 或者,ideone.com/otgyc -- 在整个表达式周围放置一组额外的括号: my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c \" var7=测试 var3=你好"; while($string =~ /((?:^|\s+)(\S+\s*=\s*"[^"]*"|\S*))/g) print "\n "; 用一个提取完整 var=value 分配的单行代码更新了帖子。【参考方案2】:对于正则表达式,使用一种我喜欢称之为 tack-and-stretch 的技术:锚定你知道会出现的特征(tack),然后抓取它们之间的内容(stretch)。
在这种情况下,您知道单个分配匹配
\b\w+=.+
您在$string
中重复了其中的许多内容。请记住,\b
表示单词边界:
单词边界 (
\b
) 是两个字符之间的一个点,其一侧有一个\w
,另一侧有一个\W
(按任意顺序),将虚构字符计算在内字符串的开头和结尾与\W
匹配。
用正则表达式描述赋值中的值可能有点棘手,但您也知道每个值都将以空格结尾——尽管不一定是遇到的第一个空格!——然后是另一个赋值或 end-of -字符串。
为避免重复断言模式,请使用 qr//
编译一次,并在您的模式中重复使用它和 look-ahead assertion (?=...)
以将匹配拉伸到足以捕获整个值,同时防止它溢出到下一个变量名。
在列表上下文中使用 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=你好 3: var3="a, b, c" 4: var7=测试 5: var3=你好
【讨论】:
感谢您的贡献。尝试了您的解决方案,它也对我有用-谢谢! +1。还要感谢您建议您对正则表达式构建的系统方法/技术:“tack-and-stretch:锚定您知道将存在的功能(tack),然后抓住两者之间的内容(stretch)。”当我有更多时间和反馈时,我会更深入地阅读您的答案。 @Rob 我很高兴它有帮助。享受吧! +1 这很好地解释了你是如何解决这个问题的。【参考方案3】:我并不是说这是你应该做的,但你想做的是写一个语法。现在,您的示例对于语法来说非常简单,但是Damian Conway 的模块Regexp::Grammars 在这方面真的非常出色。如果你必须发展它,你会发现它会让你的生活更轻松。我在这里经常使用它——它有点像 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'
]
【讨论】:
+1 因为我喜欢语法概念的想法(在计算机科学中对它们进行了一定程度的研究),尽管我没有尝试过这个答案。我喜欢语法概念,因为这种方法可以用来解决更复杂的问题,特别是在从遗留的过时语言解析代码/数据,以迁移到新的语言或数据驱动的系统/数据库——这实际上是我的原因原始问题(虽然我当时没有提到它。) 欢迎您查看此模块。正则表达式经常模糊成语法——如果你打算用正则表达式编写语法(不是一个坏主意),那么这个模块就真的死了。查看my application of it to parse theCOPY
command in my psql shell。【参考方案4】:
也许有点过头了,但我有理由研究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::REquoted/
VARIABLE: /var\d+/
VALUE: ( QSTRING | STRING | INTEGER )
assignment: VARIABLE "=" VALUE /[\s]*/ print "$itemVARIABLE => $itemVALUE\n";
startrule: assignment(s)
_EOGRAMMAR_
;
$Parse::RecDescent::skip = '';
my $parser = Parse::RecDescent->new($grammar);
my $code = qvar1=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。注意 double var3,如果您希望后一个赋值覆盖第一个赋值,您可以使用散列来存储值,然后再使用它们。
PPS。我的第一个想法是拆分 '=' 但如果字符串包含 '=' 则会失败,并且由于正则表达式几乎总是不利于解析,所以我最终尝试了它并且它有效。
编辑:在带引号的字符串中添加了对转义引号的支持。
【讨论】:
感谢您的回答。我需要在我的特定系统上安装 Parse 模块才能试用它。因此,我倾向于没有这种依赖的解决方案。【参考方案5】:我最近不得不解析 x509 证书的“主题”行。它们的形式与您提供的相似:
echo 'Subject: C=HU, L=Budapest, O=Microsec Ltd., CN=Microsec e-Szigno Root CA 2009/emailAddress=info@e-szigno.hu' | \
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/emailAddress=info@e-szigno.hu
正则表达式的简短描述:
(\w+\=.+?)
- 以非贪婪模式捕获后跟“=”的单词和任何后续符号(?=(?:, \w+\=|$))
- 后跟另一个 , KEY=val
或行尾。
使用的正则表达式的有趣部分是:
.+?
- 非贪婪模式
(?:pattern)
- 非捕获模式
(?=pattern)
零宽度正向前瞻断言
【讨论】:
【参考方案6】:这也将为您提供双引号中常见的转义,例如 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
【讨论】:
【参考方案7】:您要求提供正则表达式解决方案或其他代码。这是一个(大部分)仅使用核心模块的非正则表达式解决方案。唯一的正则表达式是\s+
来确定分隔符;在这种情况下,一个或多个空格。
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 上的代码就是加油!
【讨论】:
【参考方案8】:#!/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;
【讨论】:
我认为使用Text::ParseWords
而不是Text::Shellwords
会更好。 Text::ParseWords
具有类似的功能,但它是 Perl 核心的一部分。
@drewk 感谢您的提醒。我总是混淆这两者。我将更新示例以使用Text::ParseWords
。
对我来说很好。请参阅此评论中的进一步输出。这取决于一个模块——我很幸运在我的机器上存在这个模块,但是对于某些 Perl 模块来说,这并不总是在每个发行版/平台上都能得到保证。这是输出: --- - var1=100 - var2=90 - var5=hello - 'var3=a, b, c' - var7=test - var3=hello --- - var1: 100 - var2: 90 - var5 : 你好 - var3: 'a, b, c' - var7: 测试 - var3: 你好
@Rob:我认为Text::ParseWords
自 5.00 以来一直是核心发行版的一部分。 shellwords 功能非常有用,在 5.00 之前,许多人使用 shell eval 来实现这一点,即使有安全风险。从 5.00 起不再需要这样做了。
@Rob:问问自己哪个更易于维护:复杂模式、自定义解析器或核心模块依赖项。【参考方案9】:
使用正则表达式可以做到这一点,但是它很脆弱。
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;
【讨论】:
可能需要在此处添加一些缺少的内容或更正一些内容,因为我在运行它时收到一条错误消息:ideone.com/4bR1b 并且也在我自己的机器上。 在 ./regex_solution.pl 第 8 行,“qr/( (?:\w+=[\w\,]+) | ( ./regex_solution 处的语法错误。 pl 第 8 行,在 "qr/( (?:\w+=[\w\,]+) | (?:\w+=\"[^\"]*\") )/xg" 附近执行 ./regex_solution .pl 由于编译错误而中止。以上是关于Perl 中的正则表达式组:如何从正则表达式组中捕获与字符串中出现的未知数量/多个/变量匹配的元素到数组中?的主要内容,如果未能解决你的问题,请参考以下文章