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

What is a non-capturing group in regular expressions?

提问人:never_had_a_name 提问时间:8/18/2010 最后编辑:AndrewGBnever_had_a_name 更新时间:7/29/2023 访问量:1190355

问:

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

捕获 正则表达式组

评论

70赞 aliteralmind 4/10/2014
此问题已添加到 Stack Overflow 正则表达式常见问题解答的“组”下。

答:

159赞 RC. 8/18/2010 #1

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

例如,匹配 IP 地址的内容:

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

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

评论

17赞 Francisco Zarabozo 8/28/2020
对于没有经验的读者:这将匹配 IP 地址,但也匹配无效的 IP 地址。验证 IP 地址的表达式要复杂得多。因此,请勿使用它来验证 IP 地址。
0赞 Amber 2/24/2022
补充一点,这是说你有 1 到 3 位数字,后跟一个“.”,正好是三次,然后是另外 1 到 3 位数字。
0赞 Marcos Camargo 12/15/2023
PCRE(Perl)和ER(扩展正则表达式)中的非捕获组有什么区别?
24赞 Bob Fincheimer 8/18/2010 #2

捕获的组可以稍后在正则表达式中使用以匹配,也可以在正则表达式的替换部分中使用它们。创建非捕获组只是免除了出于上述任一原因而使用该组的权限。

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

这几乎就是它们存在的原因。当你在学习群时,了解原子群,他们做了很多事情!也有环顾小组,但它们有点复杂,使用得不多。

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

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

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

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

评论

1赞 never_had_a_name 8/18/2010
您能举一个简单的例子来说明以后将如何使用它来匹配 OR 吗?
1赞 Bob Fincheimer 8/18/2010
我的意思是你可以以后用它来匹配,或者你可以在替换中使用它。那句话中的“或”只是为了向你展示捕获组有两种用途
47赞 sepp2k 8/18/2010 #3

它使该组不捕获,这意味着该组匹配的子字符串将不会包含在捕获列表中。用 ruby 举个例子来说明其中的区别:

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

评论

0赞 PRASANNA SARAF 8/5/2019
为什么我们不能只使用“abc”.match(/.(.)。/).捕获在这里?
1赞 sepp2k 8/6/2019
@PRASANNASARAF 当然可以。代码的要点是要表明它不会产生捕获,而不是要演示一个有用的 示例。 当您想要对子表达式进行分组时(例如,当您想要将量词应用于非原子子表达式时,或者想要限制 ),但不想捕获任何内容时,它很有用。(?:)(?:)(?:)|
244赞 Bill the Lizard 8/18/2010 #4

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

假设您要匹配数字文本,但某些数字可以写成第 1、2、3、4 ,...如果要捕获数字部分,但不捕获(可选)后缀,则可以使用非捕获组。

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

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

评论

2赞 Timo 3/17/2021
如果没有非捕获组,我可以做: ?我有号码,不需要。顺便说一句,最后是什么?([0-9]+)(st|nd|rd|th)?\1?:?
1赞 Pillager225 12/10/2021
在这种情况下,末尾表示捕获组是可选的。?
2992赞 Ricardo Nolde 8/18/2010 #5

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

请考虑以下文本:

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(请注意,正则表达式可能不是这项工作的最佳工具,但它是一个很好的例子)。你想解析标签,所以你可以做这样的事情(我添加了空格以使其更容易理解):

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

评论

4赞 Steve Wortham 8/19/2010
@ajsie:如果要对结果执行替换操作,则传统(捕获)组最有用。这里有一个例子,我抓取逗号分隔的姓氏和名字,然后颠倒它们的顺序(感谢命名组)......regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
6赞 Yevgeniy 5/8/2014
还可以指出,当使用正则表达式作为拆分分隔符时,非捕获组特别有用:“Alice and Bob”-split“\s+(?:and|or)\s+”
13赞 Christian 5/12/2014
解释非捕获组 (?:) 与前瞻和后视断言 (?=, ?!) 之间的区别会很有趣。我刚刚开始学习正则表达式,但据我了解,非捕获组用于匹配并“返回”它们匹配的内容,但“返回值”不会“存储”用于反向引用。另一方面,前瞻和后视断言不仅没有“存储”,它们也不是匹配的一部分,它们只是断言某些东西会匹配,但它们的“匹配”值被忽略了,如果我没记错的话......(我大致正确吗?
9赞 Ricardo Nolde 6/6/2014
[] 是一个集合;[123] 匹配集合内的任何字符一次;[^123] 匹配一次不在集合内的任何内容;[^/\r\n]+ 匹配一个或多个不同于 /、\r、\n 的字符。
1赞 luke 3/17/2021
同样重要的是,具有非捕获组的正则表达式比具有捕获组 '(') 的相同正则表达式要快得多。因此,当我们不需要捕获组时,我们应该使用非捕获组。(?:
9赞 Jack Peng 3/9/2014 #6

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

68赞 user2369060 2/4/2016 #7

历史动机:

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

考虑表达式 和 ,由于串联的优先级,这些表达式分别表示两种不同的语言。(a|b)ca|bc|{ac, bc}{a, bc}

但是,括号也用作匹配组(如其他答案所解释的那样......

如果要使用括号但不捕获子表达式,请使用 NON-CAPTURING GROUPS。在此示例中,(?:a|b)c

评论

0赞 Behnam Esmaili 4/24/2023
唯一的答案暗示了为什么我们需要分组。+1
11赞 Gaurav 3/1/2016 #8

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

考虑一个场景,当您想要匹配猫和动物时,您想要匹配,并且两者之间都应该有一个。cat is animalis

 // 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"]
55赞 shekhar gehlot 1/19/2017 #9

Let me try this with an example:

Regex Code: (?:animal)(?:=)(\w+)(,)\1\2

Search String:

Line 1 - animal=cat,dog,cat,tiger,dog

Line 2 - animal=cat,cat,dog,dog,tiger

Line 3 - animal=dog,dog,cat,cat,tiger

(?:animal) --> Non-Captured Group 1

(?:=)--> Non-Captured Group 2

(\w+)--> Captured Group 1

(,)--> Captured Group 2

\1 --> result of captured group 1 i.e In Line 1 is cat, In Line 2 is cat, In Line 3 is dog.

\2 --> result of captured group 2 i.e comma (,)

So in this code by giving and we recall or repeat the result of captured group 1 and 2 respectively later in the code.\1\2

As per the order of code should be group 1 and should be group 2 and continues..(?:animal)(?:=)

but by giving the we make the match-group non captured (which do not count off in matched group, so the grouping number starts from the first captured group and not the non captured), so that the repetition of the result of match-group can't be called later in code.?:(?:animal)

Hope this explains the use of non capturing group.

enter image description here

评论

0赞 Jeter-work 4/13/2023
(?:animal=)(\w+)(,)\1\2 Since the only match for is , there is no reason to have two non-capturing groups. On the other hand, if you want to capture the first word, would do the same matching of repeating the first animal and comma. E.g. if your text also includes vegetables and minerals, you could use and then you have the category or whatever you want to call the word before the equals, in group 1. The non-capturing group in this case would be only the and this would be thrown away.(?:animal)(?:=)animal=(animal)(?:=)(\w+)(,)\2\3(?:\w+)(?:=)(\w+)(,)\2\3(?:=)
4赞 Harini 5/23/2017 #10

I think I would give you the answer. Don't use capture variables without checking that the match succeeded.

The capture variables, , etc, are not valid unless the match succeeded, and they're not cleared, either.$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";
}

In the above example, to avoid capturing bronto in , is used.$1(?:)

If the pattern is matched , then is captured as next grouped pattern.$1

So, the output will be as below:

Fred wants a burger

It is Useful if you don't want the matches to be saved.

12赞 RBT 7/15/2017 #11

To complement other good answers in this thread, I want to add an interesting observation that I came across.

Finding: You can have a capturing group inside a non-capturing group.

Problem Details: Have a look at below regex for matching web urls:

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

Here is my input url string:

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

The first group in my regex is a non-capturing group which matches the protocol scheme () and colon () character. In continuation it becomes . But when I ran below code:(?:([A-Za-z]+):)http:http:

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

I could see that the 1st index of the returned array was containing the string instead (Refer screenshot).http

enter image description here

At this point, my thinking was that and colon both will not get reported in the output as they are inside a non-capturing group. If the first regex group is a non-capturing group then why it is returning string in the output array?http:(?:([A-Za-z]+):)http

Explanation: So if you notice, is a capturing group (not having at the beginning). But this capturing group is itself inside a non-capturing group followed by a character. That's why the text still gets captured but the colon character which is falling inside the non-capturing group (but outside the capturing group ) doesn't get reported in the output array.([A-Za-z]+)?:(?:([A-Za-z]+):):http:

15赞 Scott Anderson 1/2/2018 #12

I cannot comment on the top answers to say this: I would like to add an explicit point which is only implied in the top answers:

The non-capturing group does not remove any characters from the original full match, it only reorganises the regex visually to the programmer.(?:...)

To access a specific part of the regex without defined extraneous characters you would always need to use .group(<index>)

评论

3赞 Seshadri R 7/12/2018
You have provided the most important hint that was missing in the rest of the answers. I tried all the examples in them and using the choicest of expletives, as I did not get the desired result. Only your posting showed me where I went wrong.
3赞 AmerllicA 5/7/2018 #13

Open your Google Chrome devTools and then Console tab: and type this:

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

Run it and you will see:

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

The RegExp engine capture three groups, the items with indexes 1,2,3. Now use non-capturing mark to see the result.JavaScript

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

The result is:

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

This is obvious what is non capturing group.

19赞 Aaron S 5/11/2018 #14

tl;dr non-capturing groups, as the name suggests are the parts of the regex that you do not want to be included in the match and is a way to define a group as being non-capturing.?:

Let's say you have an email address . The following regex will create two groups, the id part and @example.com part. . For simplicity's sake, we are extracting the whole domain name including the character.[email protected](\p{Alpha}*[a-z])(@example.com)@

Now let's say, you only need the id part of the address. What you want to do is to grab the first group of the match result, surrounded by in the regex and the way to do this is to use the non-capturing group syntax, i.e. . So the regex will return just the id part of the email.()?:(\p{Alpha}*[a-z])(?:@example.com)

评论

3赞 metablaster 10/22/2020
I was struggling to understand all those answers here until I scrolled down to yours!
2赞 Jeter-work 4/13/2023
Non-capturing groups are included in the match. But they're not captured. This means that when you list the captured groups, the non-captured group(s) are not in that list. They are also not available for referencing later in the expression. I.e. if you did not care about the domain, you could use and you'd only capture the ID portion. But if it has to be example.com, and you want to throw away the example.com because all the good matches have it, you'd use the non-capturing group. .(\w+)@\w.\w.*(\w+)@(?:example.com)
1赞 saxbophone 6/5/2023
@Jeter-work 's comment here is concise and readable enough in of itself that it ought to be an answer to the question!
5赞 Naved Ahmad 1/7/2019 #15

Its extremely simple, We can understand with simple date example, suppose if the date is mentioned as 1st January 2019 or 2nd May 2019 or any other date and we simply want to convert it to dd/mm/yyyy format we would not need the month's name which is January or February for that matter, so in order to capture the numeric part, but not the (optional) suffix you can use a non-capturing group.

so the regular expression would be,

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

Its as simple as that.

6赞 user8998146 4/24/2021 #16

(?: ... ) acts as a group ( ... ) but doesn't capture the matched data. It's really much more efficient than a standard capture group. It is use when you want to group something but don't need to reuse it later. @Toto

22赞 Cyberwip 7/8/2021 #17

A Simple Answer

Use them to ensure one of several possibilities occur here or an optional phrase or in general, anywhere you want to establish a group/phrase/section without needing to refer to it specifically.(?:one|two)camp(?:site)?

They keep your captured group(s) count to a minimum.

7赞 Kanagavelu Sugumar 1/24/2022 #18

Let me take to you an example of geo coordinate, the below matches two groups

Latitude,Longitude

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

Lets take one ([+-]?\d+(?:\.\d+)?)

co-ordinate can be whole number like or could be
Hence the optional () second part is mentioned.
5858.666.666(\.\d+)?

(...)? - for optional

But it is parenthesised, that will be another group of match. and we dont want two matches one for and another for , we need single latitude as match. Here comes non-capturing group 58.666(?:)

with non-capturing group , 58.666 and 58 both are single match[+-]?\d+(?:\.\d+)?