繁体   English   中英

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中,如何使用一个正则表达式分组来捕获多个匹配它的事件到多个数组元素?

例如,对于字符串:

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”但没有足够的线索时,已经查看了现有的答案:

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*)/匹配两个引号,其中包含任意数量的字符,或者该值的任意数量的非空格字符。请注意,引用匹配非常脆弱并且赢了正确处理escpaped引号,例如"\\"quoted\\""将导致"\\"

编辑:

既然你真的想得到整个作业,而不是单个键/值,这里有一个单行提取:

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

使用正则表达式,使用我喜欢称为弹力和伸展的技术:锚定在你知道将要存在的特征(大头钉)上,然后抓住(拉伸)之间的内容。

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

\b\w+=.+

你在$string重复了很多这些。 请记住\\b表示单词边界:

单词边界( \\b )是两个字符之间的一个点,在它的一边有一个\\w ,另一边有一个\\W (按任意顺序),计算虚构字符的开头和结尾。字符串匹配\\W

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

为了避免重复断言模式,使用qr//编译一次并在模式中重用它以及前瞻断言(?=...)以将匹配拉伸到足以捕获整个值,同时还防止它从溢出到下一个变量名称。

使用m//g匹配列表上下文中的模式会产生以下行为:

/g修饰符指定全局模式匹配 - 即在字符串中尽可能多地匹配。 它的行为取决于上下文。 在列表上下文中,它返回正则表达式中任何捕获括号匹配的子字符串列表。 如果没有括号,则返回所有匹配字符串的列表,就好像整个模式周围有圆括号一样。

模式$assignment使用非贪婪.+? 一旦前瞻看到另一个任务或行尾,就切断价值。 请记住,匹配返回所有捕获子模式的子字符串,因此前瞻的交替使用非捕获(?:...) 相反, 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

我不是说这是你应该做的,但你要做的就是写一个语法 现在你的例子对于语法非常简单,但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'
                      }
                    ]

有点过头了,但是我可以借此调查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。 请注意double var3,如果您希望后一个赋值覆盖第一个,您可以使用哈希来存储值,然后再使用它们。

PPS。 我的第一个想法是分裂'=',但是如果一个字符串包含'='并且因为正则表达式几乎总是对解析不好,那么会失败,所以我最终尝试了它并且它有效。

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

我最近不得不解析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)零宽度正向前瞻断言

这个将为您提供双引号中的常见转义,例如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;

您要求提供RegEx解决方案或其他代码。 这是一个(大多数)非正则表达式解决方案,仅使用核心模块。 唯一的正则表达式是\\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";
}

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

输出是:

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

如果你真的想要一个正则表达式的解决方案,艾伦摩尔的评论链接到他在IDEone上的代码就是天然气!

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

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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM