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

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

1073733 次浏览

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

一个例子是匹配IP地址的东西:

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

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

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

如果您尝试捕获许多不同的东西,并且有一些您不想捕获的组,则非捕获组非常棒。

这就是它们存在的原因。当你学习组的时候,了解原子群,他们做了很多!还有环顾组,但它们有点复杂,没有那么多使用。

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

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

([A-Z][A-Z0-9]*)是一个捕获组(在这种情况下,它是标记名)

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

它使组成为非捕获,这意味着该组匹配的子字符串将不包括在捕获列表中。ruby中的一个例子来说明区别:

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

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

假设您想匹配数字文本,但有些数字可以写成1、2、3、4……如果您想捕获数字部分,但不是(可选的)后缀,您可以使用非捕获组。

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

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

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

考虑以下文本:

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或超文本标记语言(请注意Regex可能不是这项工作的最佳工具,但它是一个很好的例子)。您想解析标签,所以您可以这样做(我添加了空格以使其更容易理解):

   \<(?<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/,它提供了有关正则表达式如何工作的大量详细信息;它还提供了一些正则表达式引擎可供选择。

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

历史动机:

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

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

然而,括号也被用作匹配组(如其他答案所解释的…)。

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

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

考虑一个你想匹配cat is animal的场景当你想匹配猫和动物,两者之间应该有一个is

 // 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"]

让我用一个例子来试试:

正则表达式代码:(?: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行是cat,第2行是cat,第3行是狗。

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

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

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

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

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

在此处输入图片描述

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

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

#!/usr/bin/perluse 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中捕获bron,使用了(?:)

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

因此,输出将如下所示:

Fred wants a burger

这是有用的,如果你不希望比赛被保存。

为了补充这篇文章中的其他好答案,我想添加一个我遇到的有趣的观察。

找到:您可以拥有捕获组里面非捕获组。

问题详情:查看下面的正则表达式以匹配Web URL:

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

这是我的输入url字符串:

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

我的正则表达式(?:([A-Za-z]+):)中的第一个组是一个非捕获组,它与协议方案(http)和冒号(:)字符匹配。在继续中,它变成了http:。但是当我运行下面的代码时:

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

我可以看到返回数组的1st索引包含字符串http(请参阅屏幕截图)。

输入图片描述

在这一点上,我的想法是http和冒号:都不会在输出中报告,因为它们在非捕获组中。如果第一个正则表达式组(?:([A-Za-z]+):)是非捕获组,那么为什么它在输出数组中返回http字符串?

补充说明:所以如果你注意到,([A-Za-z]+)是一个捕获组(开头没有?:)。但是这个捕获组本身在一个非捕获组(?:([A-Za-z]+):)中,后面跟着一个:字符。这就是为什么文本http仍然被捕获,但是属于非捕获组(但在捕获组之外)的冒号:字符不会在输出数组中报告。

我不能评论上面的答案:我想补充一个明确的观点,这只隐含在上面的答案中:

非捕获组(?...)没有删除是否与原始完全匹配中的任何字符匹配,它只将可视地重新组织正则表达式给程序员。

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

打开您的GoogleChromedevTools,然后打开Console选项卡:并键入以下内容:

"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]

这是显而易见的什么是非捕获组。

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

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

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

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

所以正则表达式应该是,

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

就这么简单。

(?:…)充当组(…)但不捕获匹配的数据。它真的比标准捕获组高效得多。当你想对某些内容进行分组但以后不需要重复使用时,可以使用它。@Toto

一个简单的答案

使用它们来确保几种可能性中的一种出现在这里(?:one|two)或可选短语camp(?:site)?或一般情况下,在您想要建立组/短语/部分的任何地方,而无需具体引用它。

他们保持你的捕获组(S)计数到最低限度。

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

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

取一个([+-]?\d+(?:\.\d+)?)

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

(...)? - for optional

我们不需要两个匹配,一个为58,另一个为.666,我们需要单个纬度作为匹配。这里是非捕获组(?:)

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