3

我有一个正则表达式列表(大约 10 - 15),我需要匹配一些文本。在一个循环中一一匹配它们太慢了。但是,我没有编写自己的状态机来一次匹配所有的正则表达式,而是尝试|单独的正则表达式并让 perl 完成工作。问题是我怎么知道哪些选择匹配?

这个问题解决了每个单独的正则表达式中没有捕获组的情况。(哪个部分与正则表达式匹配?)如果每个正则表达式中都有捕获组怎么办?

所以有了以下内容,

/^(A(\d+))|(B(\d+))|(C(\d+))$/

和字符串“A123”,我怎么知道 A123 匹配并提取“123”?

4

5 回答 5

5

为什么不使用/^ (?<prefix> A|B|C) (?<digits> \d+) $/x. 请注意,命名捕获组是为了清楚起见,而不是必需的。

于 2011-11-09T17:34:52.060 回答
5

您不需要编写自己的状态机来组合正则表达式。查看Regexp:Assemble。它具有跟踪您的初始模式匹配的方法。

编辑:

use strict;
use warnings;

use 5.012;

use Regexp::Assemble;

my $string = 'A123';

my $re = Regexp::Assemble->new(track => 1);
for my $pattern (qw/ A(\d+) B(\d+) C(\d+) /) {
  $re->add($pattern);
}

say $re->re; ### (?-xism:(?:A(\d+)(?{0})|B(\d+)(?{2})|C(\d+)(?{1})))
say for $re->match($string); ### A(\d+)
say for $re->capture; ### 123
于 2011-11-09T17:44:29.297 回答
3

A123将在捕获组中$1并将123在组中$2

所以你可以说:

if ( /^(A(\d+))|(B(\d+))|(C(\d+))$/ && $1 eq 'A123' && $2 eq '123' ) {
    ...
}

这是多余的,但你明白了......

编辑:不,您不必枚举每个子匹配,您询问如何知道是否A123匹配以及如何提取123

  • 除非匹配,否则您不会进入if区块A123
  • 您可以123使用$2反向引用进行提取。

所以也许这个例子会更清楚:

if ( /^(A(\d+))|(B(\d+))|(C(\d+))$/ ) {
    # do something with $2, which will be '123' assuming $_ matches /^A123/
}

编辑2:

要在 AoA 中捕获匹配项(这是一个不同的问题,但应该这样做):

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @matches = map { [$1,$2] if /^(?:(A|B|C)(\d+))$/ } <DATA>;
print Dumper \@matches;

__DATA__
A123
B456
C769

结果:

$VAR1 = [
          [
            'A',
            '123'
          ],
          [
            'B',
            '456'
          ],
          [
            'C',
            '769'
          ]
        ];

请注意,我修改了您的正则表达式,但从您的评论来看,这似乎就是您想要的...

于 2011-11-09T17:34:23.803 回答
1

使用您的示例数据,很容易编写

'A123' =~ /^([ABC])(\d+)$/;

之后 $1 将包含前缀和 $2 后缀。

我无法判断这是否与您的真实数据相关,但使用额外的模块似乎有点矫枉过正。

于 2011-11-10T01:47:09.337 回答
1

您可以在 Perl 中做的另一件事是使用“(?{...})”将 Perl 代码直接嵌入到您的正则表达式中。因此,您可以设置一个变量来告诉您正则表达式的哪一部分匹配。警告:您的正则表达式不应包含任何变量(嵌入的 Perl 代码之外),这些变量将被插入到正则表达式中,否则您将得到错误。这是一个使用此功能的示例解析器:

my $kind;
my $REGEX  = qr/
          [A-Za-z][\w]*                        (?{$kind = 'IDENT';})
        | (?: ==? | != | <=? | >=? )           (?{$kind = 'OP';})
        | -?\d+                                (?{$kind = 'INT';})
        | \x27 ( (?:[^\x27] | \x27{2})* ) \x27 (?{$kind = 'STRING';})
        | \S                                   (?{$kind = 'OTHER';})
        /xs;

my $line = "if (x == 'that') then x = -23 and y = 'say ''hi'' for me';";
my @tokens;
while ($line =~ /( $REGEX )/xsg) {
    my($match, $str) = ($1,$2);
    if ($kind eq 'STRING') {
        $str =~ s/\x27\x27/\x27/g;
        push(@tokens, ['STRING', $str]);
        }
    else {
        push(@tokens, [$kind, $match]);
        }
    }
foreach my $lItems (@tokens) {
    print("$lItems->[0]: $lItems->[1]\n");
    }

打印出以下内容:

IDENT: if
OTHER: (
IDENT: x
OP: ==
STRING: that
OTHER: )
IDENT: then
IDENT: x
OP: =
INT: -23
IDENT: and
IDENT: y
OP: =
STRING: say 'hi' for me
OTHER: ;

这有点做作,但是您会注意到字符串周围的引号(实际上是撇号)被剥离(而且,连续的引号被折叠为单引号),所以一般来说,只有 $kind 变量会告诉您解析器是否看到一个标识符或一个带引号的字符串。

于 2016-03-11T16:57:02.747 回答