在Perl中如何将正则表达式匹配结果存储到数组中?

73

是否可能将正则表达式的所有匹配项存储到数组中?

我知道可以使用($1,...,$n) = m/expr/g;,但似乎只有在您知道要查找的匹配项数量时才能使用。我尝试过my @array = m/expr/g;,但似乎不起作用。


10
解释“doesn't seem to work”,最好用一个实际的例子。那个应该起作用。 - ysth
使用 ($1, ...) = ... 是一个非常糟糕的例子,应该进行编辑。这会让人感到困惑,因为捕获组已经设置了 $1。那么你为什么要重新赋值呢(如果允许的话)? - U. Windl
7个回答

88

如果您正在进行全局匹配(/g),则在列表上下文中使用的正则表达式将返回所有捕获的匹配项。只需执行以下操作:

my @matches = ( $str =~ /pa(tt)ern/g )

比如这个命令:

perl -le '@m = ( "foo12gfd2bgbg654" =~ /(\d+)/g ); print for @m'

给出输出:

12
2
654

3
如果您尝试在Windows“shell”中运行此命令,请确保使用引号,就像这样:perl -le "@m = ( 'foo12gfd2bgbg654' =~ /(\d+)/g ); print for @m",否则会出现错误,因为shell会将双引号用作字符串分隔符。 - roamcel
不幸的是,它对于像稍微修改过的示例中的替换操作不起作用:perl -le '@m = ( (my $s = "foo12gfd2bgbg654") =~ s/(\d+)//g ); print for @m' 只会打印出 3 - U. Windl
1
@U.Windl,这并不是“稍作修改”。您得到的是s/.../.../g的返回值,它返回所做的替换次数。唯一可以在替换中使用捕获值的地方是替换值。例如:s/(\d+)/找到数字 $1/g - Francisco Zarabozo

19

有时候需要全局匹配所有符合条件的结果,类似于 PHP 中的 preg_match_all 函数。如果你需要这样做,可以写出如下代码:

# a dummy example
my $subject = 'Philip Fry Bender Rodriguez Turanga Leela';
my @matches;
push @matches, [$1, $2] while $subject =~ /(\w+) (\w+)/g;

use Data::Dumper;
print Dumper(\@matches);

它打印出来

$VAR1 = [
          [
            'Philip',
            'Fry'
          ],
          [
            'Bender',
            'Rodriguez'
          ],
          [
            'Turanga',
            'Leela'
          ]
        ];

3
非常方便的技巧;如果捕获组的数量未知,有没有一种泛化的方法?看起来你可能需要一个特殊的数组变量,其中包括 ($1, $2, ...),但我找不到这样的东西。 请问是否可以让这个技巧更加通用化,以应对捕获组数量未知的情况?似乎需要使用一个特殊的数组变量,将所有捕获组都包含进去(如 $1, $2 等),但我找不到这样的变量。 - mklement0
2
@mklement0 在 Perl 5.25.7 中添加了变量@{^CAPTURE},它包含最后一个成功匹配的($1, $2, ...) 。为了概括上面的答案,请执行 push @matches, [@{^CAPTURE}] while $subject =~ /(\w+) (\w+)/g; - Viktor Söderqvist

18

请参阅perldoc perlop中“匹配列表上下文”部分的手册:

如果没有使用/g选项,m//在列表上下文中返回一个列表,其中包含模式中括号匹配到的子表达式,即($1, $2, $3 ...)。

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

您可以通过分配到数组或以列表上下文执行评估来简单地获取所有匹配项:

my @matches = ($string =~ m/word/g);

10

我认为这是一个不需要解释的例子。请注意第一个正则表达式中的 /g 修饰符:

$string = "one two three four";

@res = $string =~ m/(\w+)/g;
print Dumper(@res); # @res = ("one", "two", "three", "four")

@res = $string =~ m/(\w+) (\w+)/;
print Dumper(@res); # @res = ("one", "two")

请记住,您需要确保左值在列表上下文中,这意味着您必须使用括号将标量值括起来:
($one, $two) = $string =~ m/(\w+) (\w+)/;

3
是否可以将正则表达式的所有匹配结果存储在一个数组中?是的,在Perl 5.25.7中,变量@{^CAPTURE}被添加了进来,它保存着“上一次成功匹配的模式的捕获缓冲区内容(如果有的话)”即展示了($1, $2, ...)的内容即使不确定捕获组数。 在Perl 5.25.7之前(自5.6.0版本以来),您可以使用@-@+构建相同的数组,如@Jaques在他的答案中所建议的那样。 这需要做以下操作:
    my @capture = ();
    for (my $i = 1; $i < @+; $i++) {
        push @capture, substr $subject, $-[$i], $+[$i] - $-[$i];
    }

3

我很惊讶这里还没有提到,但是Perl文档提供了标准变量@+。引用文档内容:

此数组保存当前活动动态范围内最后一个成功的子匹配的起始偏移量。

因此,要获取第一个捕获到的值,可以编写以下代码:

print substr( $str, $-[1], $+[1] - $-[1] ), "\n"; # equivalent to $1

作为附注,还有一个非常方便的标准变量%-,它不仅包含命名捕获,而且允许重复名称存储在数组中。
使用文档提供的示例:
/(?<A>1)(?<B>2)(?<A>3)(?<B>4)/

会产生一个哈希值,其中包含以下条目:
$-{A}[0] : '1'
$-{A}[1] : '3'
$-{B}[0] : '2'
$-{B}[1] : '4'

0
请注意,如果您知道每个匹配需要的捕获组数量,您可以使用这种简单的方法,我将其作为2个捕获组的示例进行演示。
假设您有一些“数据”,例如:
my $mess = <<'IS_YOURS';
Richard     Rich
April           May
Harmony             Ha\rm
Winter           Win
Faith     Hope
William         Will
Aurora     Dawn
Joy  
IS_YOURS

使用以下正则表达式

my $oven = qr'^(\w+)\h+(\w+)$'ma;  # skip the /a modifier if using perl < 5.14

我可以在下面的@box中捕获所有12个(6对,不是8个...Harmony逃脱了,Joy失踪了)。

my @box = $mess =~ m[$oven]g;

如果我想要“敲定”盒子的细节,我可以这样做:
my %hash = @box;

或者我本来就可以完全跳过这个框。

my %hash = $mess =~ m[$oven]g;

请注意,%hash 包含以下内容。顺序丢失并且重复的键(如果存在)被压缩:
(
          'April'   => 'May',
          'Richard' => 'Rich',
          'Winter'  => 'Win',
          'William' => 'Will', 
          'Faith'   => 'Hope',
          'Aurora'  => 'Dawn'
);

这个答案没有考虑到的是未知的捕获组数量: 你只使用了两个。所以你可以始终使用($1, $2)来获取所有匹配项。 - U. Windl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接