如何捕获多个重复的组?

184
我需要捕获多个相同模式的组。假设我有以下字符串:
HELLO,THERE,WORLD

我已经写了下面的模式
^(?:([A-Z]+),?)+$

我想要的是捕获每一个单词,所以第一组是:"HELLO",第二组是:"THERE",第三组是:"WORLD"。但是我的正则表达式实际上只捕获到最后一个,也就是"WORLD"。
我正在测试我的正则表达式here,我想要在Swift中使用它(也许在Swift中有一种方法可以获取中间结果,这样我就可以使用它们?)
我不想使用split。我只需要知道如何捕获与模式匹配的所有组,而不仅仅是最后一个。

8
为什么不以逗号分割? - rock321987
为什么不使用[A-Z]+[^,]+来捕获结果? - rock321987
需要更多的输入和输出... 还不够清楚。 - rock321987
7
rock321987,有什么不清楚的吗?我希望字符串中的每个单词都成为一个匹配组,但是我的模式只捕获了最后一个单词("WORLD")。 - phbelov
1
使用此答案查找所有匹配项:https://dev59.com/aF4c5IYBdhLWcg3weaT8#27880748 - rock321987
显示剩余4条评论
11个回答

119

在一个模式中,如果只有一个组,那么你只能得到一个精确的结果。如果模式重复使用了捕获组(在周围的非捕获组上使用了+量化符号),那么只有最后匹配的值会被存储。

你必须使用你语言的正则表达式实现函数来查找所有匹配项,然后需要删除非捕获组的锚点和量化符号(如果需要,也可以省略非捕获组本身)。

或者,扩展你的正则表达式,并让模式包含每个想要在结果中获得的组的一个捕获组:

^([A-Z]+),([A-Z]+),([A-Z]+)$

52
如何根据多个字符串的数量进行调整?例如:HELLO,WORLD 和 HELLO,THERE,MY,WORLD。我正在寻找一个表达式来处理这两个示例,并具有为更长的字符串数组构建灵活性的能力。 - Chris
22
无法概括。正如答案所述,捕获组只能捕获一种内容,无法创建动态数量的捕获组。 - Barmar
2
“_如何调整以适应不同数量的字符串?_”--对于那些仍然访问此页面的人--使用所掌握语言的工具动态构建它。将子模式(在这里是([A-Z]+))作为字符串或正则表达式模式(根据语言而定),并连接N个(在这种情况下用逗号分隔),然后将其转换为正则表达式模式或仅在正则表达式中使用它(再次取决于语言)。通常这很简单。(我假设这个答案认为可以动态构建它。) - zdim
4
很遗憾,你只列出了_Alternatively_的代码,而没有列出答案本身的代码。 - dumbledad

46

关键区别在于重复一个捕获组而不是捕获一个重复的组。

正如您已经发现的那样,区别在于重复一个捕获组仅捕获最后一次迭代。捕获一个重复的组会捕获所有迭代。

在 PCRE (PHP) 中:

((?:\w+)+),?
Match 1, Group 1.    0-5      HELLO
Match 2, Group 1.    6-11     THERE
Match 3, Group 1.    12-20    BRUTALLY
Match 4, Group 1.    21-26    CRUEL
Match 5, Group 1.    27-32    WORLD

由于所有的捕获都在第一个组中,所以您只需要使用$1进行替换。

我使用了以下通用形式的正则表达式:

((?:{{RE}})+)

请参考regex101网站上的示例。


12
在你的regex101中,尝试将你的正则表达式替换为(\w+),?,它会给你相同的结果。关键在于使用了g标志,该标志重复匹配模式以匹配多个组。 - Thomas LAURENT
1
这是非常错误的。"捕获重复组会捕获所有迭代": 是的,但它将在一个匹配中捕获它们所有。 你的例子应该是((?:\w,?)+)。 你在这里有多个匹配,只是因为有g标志,正如@thomas-laurent所说。 没有办法从一个捕获组中获得多个匹配。你必须提取并preg_match_all(或等效函数)重复组。 - Pierre
@Pierre 感谢您的澄清。根据原始问题,我们必须对所需内容进行假设。首先,他说:“我想捕获每个单词,以便第1组是:HELLO…第3组是WORLD…”您的区分很重要,因为此案例需要唯一的反向引用组。上面的表格显示了所有分配给“组1”的匹配项。因此,“((?:\w+)+),?”无法工作。接着,他说:“我需要捕获与模式匹配的所有组,而不仅仅是最后一个。”启用“g”标志的((?:\w+)+),?可以实现这一点。 - ssent1
1
@ssent1,你的((?:\w+)+),?等价于(\w+),?。你的匿名分组是不会重复出现的。这是误导性的,没有像“捕获多个匹配中的重复组”这样的东西。不幸的是,在正则表达式中没有任何东西可以多次匹配同一组。只有g标志和preg_match_all可以在剩余未匹配的字符串上迭代执行正则表达式。 - Pierre
1
@Pierre 你说得对。然而,似乎仍然需要区分“重复捕获组”和“捕获重复组”的区别(https://www.regular-expressions.info/captureall.html)。从实际层面来看,这可能是功能性解决方案的一部分。最终,如果需要“防弹”解决方案,最好还是以编程方式实现。 - ssent1
显示剩余2条评论

12
我觉得你需要类似这样的东西:
b = "HELLO,THERE,WORLD"
re.findall('[\w]+',b)

在Python 3中,它将返回:
['HELLO', 'THERE', 'WORLD']

11
re.findall('\w+',b)可以缩短2个字符。 因为只有一个表达式,所以不需要字符类。 - Jean-François Fabre
3
问题并没有Python标签。 - pythonian29033

5
阅读完Byte Commander的答案后,我想介绍一个可能微小的改进:
你可以生成一个正则表达式,它将匹配n个单词(在n预先确定的情况下)。例如,如果我想匹配1到3个单词,则正则表达式为:
^([A-Z]+)(?:,([A-Z]+))?(?:,([A-Z]+))?$

将匹配下一句话,其中包含一个、两个或三个捕获组。

HELLO,LITTLE,WORLD
HELLO,WORLD
HELLO

你可以在Regex101上看到有关这个正则表达式的详细解释。
正如我所说,使用你喜欢的编程语言轻松生成任何想要的组的正则表达式。由于我不是一个熟练的Swift程序员,这里提供一个Ruby示例:
def make_regexp(group_regexp, count: 3, delimiter: ",")
  regexp_str = "^(#{group_regexp})"
  (count - 1).times.each do
    regexp_str += "(?:#{delimiter}(#{group_regexp}))?"
  end
  regexp_str += "$"
  return regexp_str
end

puts make_regexp("[A-Z]+")

话虽如此,我建议在这种情况下不要使用正则表达式,因为有许多其他出色的工具可供选择,从简单的split到根据您的需求制定的一些令牌化模式。在我看来,正则表达式不是其中之一。例如,在Ruby中,我会使用类似于str.split(",")str.scan(/[A-Z]+/)的东西。


这个正则表达式怎么样:([A-Z]+)((?:,([A-Z]+))?)+?我刚刚检查过了,对于n个捕获组来说非常完美。 - pythonian29033
1
@pythonian29033 这不符合 OP 的要求,它只会捕获第一个和最后一组。至少使用 PCRE。 - Ulysse BN

4
尝试的代码存在问题,正如所讨论的那样,只有一个捕获组在重复匹配,最终只能保留最后一次匹配。相反,应指令正则表达式在字符串中匹配(和捕获)所有模式实例,在任何正则表达式实现(语言)中都可以完成此操作。因此,请提供此匹配的正则表达式模式。 所示样本数据的定义属性是感兴趣的模式由逗号分隔,因此我们可以使用否定字符类匹配除逗号以外的任何内容。
[^,]+

并进行全局匹配(捕获),以获取字符串中的所有匹配结果。

如果您的模式需要更严格的限制,则可以调整排除列表。例如,要捕获由列出的任何标点符号分隔的单词

[^,.!-]+

这段代码从 hi,there-again! 中提取了所有单词,但没有包含标点符号。(除非 - 在一个范围内使用,如 a-z0-9,否则应该在字符类的最前面或最后面给出。)

在Python中:

import re

string = "HELLO,THERE,WORLD"

pattern = r"([^,]+)"
matches = re.findall(pattern,string)

print(matches)

在Perl(和许多其他兼容系统中),
use warnings;
use strict;
use feature 'say';

my $string = 'HELLO,THERE,WORLD';

my @matches = $string =~ /([^,]+)/g;

say "@matches";

在这个特定的例子中,实际上并不需要捕获(),因为我们收集所有匹配的内容。但它们不会有任何影响,在一般情况下是必需的。


上面的方法对于其他模式也同样适用,包括在问题中尝试的那种模式(只要删除使其过于具体的锚定)。最常见的模式是捕获所有单词(通常意味着[a-zA-Z0-9_]),模式为\w+。或者,就像在问题中一样,只获取大写 ASCII 字母的子字符串[A-Z]+


3

仅提供第二段答案的额外示例。 我不确定对于您来说在一场比赛中获得三组而不是使用一组进行三场比赛有多重要。 例如,在Groovy中:

def subject = "HELLO,THERE,WORLD"
def pat = "([A-Z]+)"
def m = (subject =~ pat)
m.eachWithIndex{ g,i ->
  println "Match #$i: ${g[1]}"
}

Match #0: HELLO
Match #1: THERE
Match #2: WORLD

1
您实际上只有一个捕获组可以匹配多次,而不是多个捕获组。
JavaScript(JS)解决方案:

let string = "HI,THERE,TOM";
let myRegexp = /([A-Z]+),?/g;       // modify as you like
let match = myRegexp.exec(string);  // js function, output described below
while (match != null) {             // loops through matches
  console.log(match[1]);            // do whatever you want with each match
  match = myRegexp.exec(string);    // find next match
}

语法:

// matched text: match[0]
// match start: match.index
// capturing group n: match[n]

正如您所看到的,这适用于任何数量的匹配。


1
我知道我的回答来得有些晚,但今天我也遇到了这个问题,并采用了以下方法解决:
^(([A-Z]+),)+([A-Z]+)$

所以第一组(([A-Z]+),)+将匹配所有重复的模式,除了最后一个([A-Z]+)将匹配最后一个。而且这将是动态的,无论字符串中有多少个重复的组。

7
这不是问题的解决方案。这个问题不是关于匹配字符串,而是关于捕获所有的组。这个正则表达式仍然只捕获第一个重复组(带逗号)中的最后一个匹配项,以及最后一组(不带逗号)中的匹配项。 - gdwarf

0
  1. 设计一个正则表达式,匹配列表中的每个特定元素而不是整个列表。使用 /g 进行应用。
  2. 遍历匹配项,清除其中混入的任何垃圾,例如列表分隔符。您可以使用另一个正则表达式,或者可以通过简单的替换子字符串方法来完成。

示例代码是 JS 的,抱歉 :) 但是思路应该足够清晰了。

    const string = 'HELLO,THERE,WORLD';

    // First use following regex matches each of the list items separately:
    const captureListElement = /^[^,]+|,\w+/g;
    const matches = string.match(captureListElement);

    // Some of the matches may include the separator, so we have to clean them:
    const cleanMatches = matches.map(match => match.replace(',',''));

    console.log(cleanMatches);

0
在正则表达式的组中重复A-Z模式。
data="HELLO,THERE,WORLD"
pattern=r"([a-zA-Z]+)"
matches=re.findall(pattern,data)
print(matches)

输出

['HELLO', 'THERE', 'WORLD']

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