ChatGPT解决这个技术问题 Extra ChatGPT

什么是正则表达式中的非捕获组?

非捕获组(即 (?:))如何在正则表达式中使用,它们有什么用?

此问题已添加到“组”下的 Stack Overflow Regular Expression FAQ

i
iliketocode

让我试着用一个例子来解释这一点。

考虑以下文本:

http://stackoverflow.com/
https://stackoverflow.com/questions/tagged/regex

现在,如果我在它上面应用下面的正则表达式......

(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

...我会得到以下结果:

Match "http://stackoverflow.com/"
     Group 1: "http"
     Group 2: "stackoverflow.com"
     Group 3: "/"

Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"

但我不关心协议——我只想要 URL 的主机和路径。因此,我更改了正则表达式以包含非捕获组 (?:)

(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

现在,我的结果如下所示:

Match "http://stackoverflow.com/"
     Group 1: "stackoverflow.com"
     Group 2: "/"

Match "https://stackoverflow.com/questions/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

看?第一组没有被抓获。解析器使用它来匹配文本,但稍后在最终结果中忽略它。

编辑:

根据要求,让我也尝试解释一下组。

好吧,团体有很多用途。它们可以帮助您从更大的匹配(也可以命名)中提取准确信息,它们可以让您重新匹配以前匹配的组,并且可以用于替换。让我们尝试一些例子,好吗?

假设您有某种 XML 或 HTML(请注意 regex may not be the best tool for the job,但它作为示例很好)。你想解析标签,所以你可以做这样的事情(我添加了空格以便于理解):

   \<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
   \<(.+?)\> [^<]*? \</\1\>

第一个正则表达式有一个命名组 (TAG),而第二个正则表达式使用一个公共组。两个正则表达式都做同样的事情:它们使用第一组中的值(标签的名称)来匹配结束标签。不同之处在于第一个使用名称来匹配值,第二个使用组索引(从 1 开始)。

现在让我们尝试一些替换。考虑以下文本:

Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.

现在,让我们使用这个愚蠢的正则表达式:

\b(\S)(\S)(\S)(\S*)\b

此正则表达式匹配至少包含 3 个字符的单词,并使用组来分隔前三个字母。结果是这样的:

Match "Lorem"
     Group 1: "L"
     Group 2: "o"
     Group 3: "r"
     Group 4: "em"
Match "ipsum"
     Group 1: "i"
     Group 2: "p"
     Group 3: "s"
     Group 4: "um"
...

Match "consectetuer"
     Group 1: "c"
     Group 2: "o"
     Group 3: "n"
     Group 4: "sectetuer"
...

因此,如果我们应用替换字符串:

$1_$3$2_$4

...在它上面,我们尝试使用第一组,添加下划线,使用第三组,然后是第二组,添加另一个下划线,然后是第四组。生成的字符串将如下所示。

L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.

您也可以使用命名组进行替换,使用 ${name}

要玩转正则表达式,我推荐 http://regex101.com/,它提供了大量关于正则表达式如何工作的详细信息;它还提供了一些正则表达式引擎可供选择。


@ajsie:如果您对结果执行替换操作,传统(捕获)组最有用。这是一个例子,我抓住逗号分隔的最后一个 &名字,然后颠倒他们的顺序(感谢命名组)... regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
还可能指出非捕获组在使用正则表达式作为拆分分隔符时特别有用: "Alice and Bob"-split"\s+(?:and|or)\s+"
解释非捕获组 (?:) 和前瞻和后瞻断言 (?=, ?!) 之间的区别会很有趣。我刚开始学习正则表达式,但据我了解,非捕获组用于匹配并“返回”它们匹配的内容,但“返回值”不是“存储”用于反向引用。另一方面,前瞻和后瞻断言不仅没有“存储”,它们也不是匹配的一部分,它们只是断言某些东西会匹配,但如果我没记错的话,它们的“匹配”值会被忽略。 . (我大致正确吗?)
[] 是一个集合; [123] 匹配集合内的任何字符一次; [^123] 匹配任何不在集合内的东西一次; [^/\r\n]+ 匹配一个或多个不同于 /、\r、\n 的字符。
很好的解释,谢谢。只是一个小标注。在答案的第一个结果片段中,第 1 组应该是“https”而不是“http”。
B
Bill the Lizard

您可以使用捕获组来组织和解析表达式。非捕获组具有第一个好处,但没有第二个好处。例如,您仍然可以说非捕获组是可选的。

假设您要匹配数字文本,但某些数字可以写为 1st, 2nd, 3rd, 4th,... 如果您想捕获数字部分,而不是(可选)后缀,您可以使用非捕获组.

([0-9]+)(?:st|nd|rd|th)?

这将匹配 1、2、3... 形式或 1st、2nd、3rd... 形式的数字,但它只会捕获数字部分。


如果没有非捕获组,我可以这样做:([0-9]+)(st|nd|rd|th)??有了 \1,我就有了号码,不需要 ?:。顺便说一句,最后的 ? 是什么?
在这种情况下,末尾的 ? 表示捕获组是可选的。
R
RC.

当您想要对表达式进行分组但不想将其保存为字符串的匹配/捕获部分时,使用 ?:

一个例子是匹配一个 IP 地址:

/(?:\d{1,3}\.){3}\d{1,3}/

请注意,我不关心保存前 3 个八位字节,但 (?:...) 分组允许我缩短正则表达式,而不会产生捕获和存储匹配项的开销。


对于没有经验的读者:这将匹配一个 IP 地址,但也匹配无效的 IP 地址。验证 IP 地址的表达式会复杂得多。因此,不要使用它来验证 IP 地址。
补充一下,这就是说你有 1 到 3 位数字,后跟一个“.”,正好是 3 次,然后是另外 1 到 3 位数字。
J
J. Scott Elblein

历史动机:

非捕获组的存在可以用括号来解释。

考虑表达式 (a|b)ca|bc,由于连接优先于 |,这些表达式代表两种不同的语言(分别为 {ac, bc}{a, bc})。

但是,括号也用作匹配组(如其他答案所述......)。

当您想要有括号但不捕获子表达式时,您使用非捕获组。在示例中,(?:a|b)c


我想知道为什么。因为我认为“为什么”对于记住这些信息至关重要。
s
sepp2k

它使组不捕获,这意味着与该组匹配的子字符串不会包含在捕获列表中。 ruby 中的一个示例来说明差异:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]

为什么我们不能在这里只使用 "abc".match(/.(.)./).captures ?
@PRASANNASARAF 当然可以。代码的重点是表明 (?:) 不会产生捕获,而不是演示 (?:) 的有用示例。当您想要对子表达式进行分组时(例如,当您想要将量词应用于非原子子表达式或想要限制 | 的范围)时,(?:) 很有用,但您不需要想要捕捉任何东西。
这是IMO最准确的答案。 (1 上)
J
J. Scott Elblein

让我用一个例子来试试这个:

正则表达式代码:(?:animal)(?:=)(\w+)(,)\1\2

搜索字符串:

第 1 行 - animal=cat,dog,cat,tiger,dog

第 2 行 - animal=cat,cat,dog,dog,tiger

第 3 行 - animal=dog,dog,cat,cat,tiger

(?:animal) -->非捕获组 1

(?:=)-->非捕获组 2

(\w+)-->捕获组 1

(,)-->捕获的第 2 组

\1 -->捕获组 1 的结果,即第 1 行是猫,第 2 行是猫,第 3 行是狗。

\2 -->捕获的第 2 组的结果,即逗号 (,)

因此,在这段代码中,通过给出 \1\2,我们稍后在代码中分别回忆或重复捕获的组 1 和 2 的结果。

按照代码的顺序,(?:animal) 应该是第 1 组,(?:=) 应该是第 2 组并继续..

但是通过给出?:,我们使匹配组不被捕获(在匹配组中不计数,因此分组编号从第一个捕获的组开始,而不是未捕获的组),这样结果的重复稍后无法在代码中调用匹配组 (?:animal)

希望这解释了非捕获组的使用。

https://i.stack.imgur.com/ngQgX.png


很好的解释。很有指导意义!!
B
Bob Fincheimer

捕获的组可以稍后在正则表达式中使用以匹配,或者您可以在正则表达式的替换部分中使用它们。建立一个非捕获组只是使该组免于出于这些原因中的任何一个使用。

如果您尝试捕获许多不同的事物并且您不想捕获某些组,则非捕获组非常有用。

这几乎就是它们存在的原因。当您了解群组时,请了解 Atomic Groups,它们会做很多事情!也有环视组,但它们稍微复杂一些,使用不多。

稍后在正则表达式中使用的示例(反向引用):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [查找 xml 标签(不支持 ns)]

([A-Z][A-Z0-9]*) 是一个捕获组(在本例中是标记名)

稍后在正则表达式中是 \1,这意味着它只会匹配第一组(([A-Z][A-Z0-9]*) 组)中的相同文本(在这种情况下它匹配结束标记)。


你能举一个简单的例子来说明以后如何使用它来匹配 OR 吗?
我的意思是您可以稍后使用 to 匹配,也可以在替换中使用它。那句话中的 or 只是为了向您展示捕获组有两种用途
A
Aaron S

tl;dr 非捕获组,顾名思义是您不希望包含在匹配中的正则表达式部分,?: 是将组定义为非捕获组的一种方式-捕获。

假设您有一个电子邮件地址 example@example.com。以下正则表达式将创建两个 ,即 id 部分和 @example.com 部分。 (\p{Alpha}*[a-z])(@example.com)。为简单起见,我们将提取包括 @ 字符在内的整个域名。

现在假设,您只需要地址的 id 部分。您要做的是抓取匹配结果的第一组,在正则表达式中被 () 包围,这样做的方法是使用非捕获组语法,即 ?:。所以正则表达式 (\p{Alpha}*[a-z])(?:@example.com) 将只返回电子邮件的 id 部分。


在我向下滚动到您的答案之前,我一直在努力理解所有这些答案!
c
clearlight

一个简单的答案

使用它们来确保此处出现多种可能性之一 (?:one|two) 或可选短语 camp(?:site)? 或一般而言,您想要建立组/短语/部分的任何地方,而无需专门引用它。

他们将您捕获的组数保持在最低限度。


S
Scott Anderson

我无法评论最重要的答案:我想添加一个仅在最重要的答案中隐含的明确观点:

非捕获组 (?...) 不删除原始完整匹配中的任何字符,它只是重新组织正则表达式以直观地呈现给程序员。

要访问没有定义无关字符的正则表达式的特定部分,您总是需要使用 .group(<index>)


您提供了其余答案中缺少的最重要的提示。我尝试了其中的所有示例并使用了最好的脏话,因为我没有得到想要的结果。只有你的帖子告诉我我哪里出错了。
很高兴听到!
A
Alan Moore

好吧,我是一名 JavaScript 开发人员,并将尝试解释其与 JavaScript 相关的意义。

考虑一个场景,当您想要匹配 cat 和 animal 并且两者之间应该有一个 is 时,您想要匹配 cat is animal

 // this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]

 // using lookahead pattern it will match only "cat" we can
 // use lookahead but the problem is we can not give anything
 // at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]

 //so I gave another grouping parenthesis for animal
 // in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]

 // we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]

J
Jack Peng

在复杂的正则表达式中,您可能会遇到希望使用大量组的情况,其中一些用于重复匹配,而另一些用于提供反向引用。默认情况下,匹配每个组的文本被加载到反向引用数组中。当我们有很多组并且只需要能够从反向引用数组中引用其中的一些时,我们可以覆盖此默认行为以告诉正则表达式某些组仅用于重复处理并且不需要捕获和存储在反向引用数组中。


R
RBT

我遇到的一件有趣的事情是,您可以在非捕获组中拥有一个捕获组。查看下面的正则表达式以匹配 Web url:

var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

输入网址字符串:

var url = "http://www.ora.com:80/goodparts?q#fragment";

我的正则表达式 (?:([A-Za-z]+):) 中的第一组是一个非捕获组,它与协议方案和冒号 : 字符即 http: 匹配,但是当我在代码下面运行时,我看到返回数组的第一个索引包含当我认为 http 和冒号 : 都不会被报告时,字符串 http 都不会被报告,因为它们位于非捕获组中。

console.debug(parse_url_regex.exec(url));

https://i.stack.imgur.com/uJher.png

我想如果第一个组 (?:([A-Za-z]+):) 是一个非捕获组,那么为什么它会在输出数组中返回 http 字符串。

因此,如果您注意到非捕获组内有一个嵌套组 ([A-Za-z]+)。该嵌套组 ([A-Za-z]+) 本身就是一个非捕获组 (?:([A-Za-z]+):) 内的捕获组(开头没有 ?:)。这就是为什么文本 http 仍然被捕获,但在非捕获组内但在捕获组外的冒号 : 字符不会在输出数组中报告。


N
Naved Ahmad

它非常简单,我们可以通过简单的日期示例来理解,假设如果提到日期为 2019 年 1 月 1 日或 2019 年 5 月 2 日或任何其他日期,我们只是想将其转换为 dd/mm/yyyy 格式,我们不需要月份的名称是一月或二月,因此为了捕获数字部分,而不是(可选)后缀,您可以使用非捕获组。

所以正则表达式是

([0-9]+)(?:January|February)?

就这么简单。


J
J. Scott Elblein

我想我会给你答案。不要在未检查匹配是否成功的情况下使用捕获变量。

除非匹配成功,否则捕获变量 $1 等无效,并且它们也不会被清除。

#!/usr/bin/perl  
use warnings;
use strict;   
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
    print "Fred wants a  $1";
}
else
{
    print "Fred dont wants a $1 $2";
}

在上面的示例中,为了避免在 $1 中捕获 bronto,使用了 (?:)

如果模式匹配,则 $1 被捕获为下一个分组模式。

因此,输出将如下所示:

Fred wants a burger

如果您不想保存匹配项,这很有用。


A
AmerllicA

打开您的 Google Chrome devTools,然后打开控制台选项卡:然后输入:

"Peace".match(/(\w)(\w)(\w)/)

运行它,你会看到:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

JavaScript RegExp 引擎捕获三个组,即索引为 1、2、3 的项目。现在使用非捕获标记来查看结果。

"Peace".match(/(?:\w)(\w)(\w)/)

结果是:

["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]

这很明显什么是非捕获组。


佚名

(?: ... ) 充当组 ( ... ) 但不捕获匹配的数据。它确实比标准捕获组更有效。当您想要对某些内容进行分组但以后不需要重用它时使用它。 @托托


K
Kanagavelu Sugumar

让我举个地理坐标的例子,下面匹配两组

Latitude,Longitude

([+-]?\d+(?:\.\d+)?),([+-]?\d+(?:\.\d+)?)

让我们拿一个([+-]?\d+(?:\.\d+)?)

坐标可以是整数,例如 58,也可以是 58.666
因此提到了可选的 (.666) 第二部分 (\.\d+)?

(...)? - for optional

但它是括号,那将是另一组比赛。我们不想要两个匹配,一个用于 58,另一个用于 .666,我们需要单个纬度作为匹配。非捕获组 (?:)
来了

与非捕获组 [+-]?\d+(?:\.\d+)?,58.666 和 58 都是单匹配