Perl正则表达式捕获重复组

10

我想要一个正则表达式,它可以匹配一行开头的内容并匹配(并返回)所有其他单词。例如,给出以下这行内容:

$line = "one two three etc";
我想要像这样的东西(但它不起作用):

我希望获得类似以下内容的效果(但是无法实现):

@matches= $line=~ /^one(?:\s+(\S+))$/;

为了返回@matches中的单词 "two"、"three" 等,我想使用正则表达式来解决,不想知道如何获取这些单词。

这似乎很简单,但我一直没有找到解决方案。


1
您不能拥有“未知”数量的捕获组。您可以捕获整个字符串 two three etc,定义 3 个捕获组,或者对每个词进行全局匹配并将其分别放入捕获组1中。 - Sam
split有什么问题吗?使用split,你的代码可能会更易读。 - Len Jaffe
分割很好,但我很失望,因为我无法弄清楚正则表达式。 - agarrubio
@LenJaffe 因为问题是如何使用正则表达式来实现。示例不相关。 - felwithe
5个回答

5
要做到这一点,您需要使用\G锚点,该锚点匹配上次匹配结束的位置。当您使用此锚点构建模式时,可以获得连续的结果:
@matches = $line =~ /(?:\G(?!\A)|^one) (\S+)/g; 

4
您不能有数量未知的捕获组。如果您尝试重复一个捕获组,则最后一个实例将覆盖捕获组的内容:
表达式:^one(?:\s+(\S+))+$ 捕获 #1: etc
或者:
表达式:^one\s+(\S+)\s+(\S+)\s+(\S+)$ 捕获 #1: two 捕获 #2: three 捕获 #3: etc

我建议先捕捉整个字符串,然后按空格分割:

或者您可以进行全局匹配,并利用\G\K


我认为你的意思是 /(?:^one|(?<!\A)\G).*?(\K\S+)/g - agarrubio
\K 是不是必要的?但它有用吗? - agarrubio
\K会丢弃左侧匹配的所有内容,因此无需捕获\S+,因为它是唯一剩下的匹配内容。 - Sam

4

很遗憾,我在Perl中尝试了正则表达式,但它不起作用... @matches为空。 - agarrubio
你说得对。我没有正确复制,所以失败了。然后,我认为这是一个 Perl 问题,因为那个链接说明那些是 PHP 特定的正则表达式。 - agarrubio
进一步的测试表明,它与第一个单词无关。例如,它也匹配这一行:$line="anything two three etc" - agarrubio
@agarrubio 第一个单词总是“one”吗? - vks

0

(?{...}) "执行代码" 特殊分组可以用于记忆必要的中间分组捕获

让我们从你的代码开始:

#!/usr/bin/perl

$line = "one two three etc";
@matches = ();
$line=~ /^one(?:\s+(\S+)(?{push @matches, $1}))+$/;
print join "\n", @matches;

@matches数组中将包含“two”,“three”,“etc”等内容。因为在部分匹配后执行(?{push @matches, $1})将捕获的值存储在这里。

更复杂的示例可以更好地说明这种方法:

#!/usr/bin/perl

while(<>) { $a .= $_; }
$a =~ m{cipher-suites:\s*\[[\r\n" ]+(?:([^\]]*?)[\r\n", ]+(?{push @r, $1}))+\]}sm;

print join "\n", @r;
__END__
cipher-suites: [
  "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
  "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
  "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
  "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
  "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
  "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
]

这将把密码提取到数组@r中。

(? {...})“执行代码”是一种非常强大的正则表达式扩展,例如可以通过匹配嵌套括号表达式扩展正则表达式。


-1

最简单的解决方案可能是事后分割

use strict;
use warnings;

my $line = "one two three etc";

my @matches = $line =~ /^one\s+(.*)/ ? split(' ', $1) : ();

use Data::Dump;
dd @matches;

输出:

("two", "three", "etc")

然而,也可以使用\G来从前一个匹配的位置继续,并因此使用/g修饰符找到所有非空格。

唯一的诀窍是记得不要让\G在字符串开头匹配,所以单词one必须匹配:

my @matches = $line =~ /(?:^one|(?<!\A)\G)\s+(\S+)/g;

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