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


Answers:


2323

让我尝试用一​​个例子来解释一下。

考虑以下文本:

http://stackoverflow.com/
/programming/tagged/regex

现在,如果我在下面套用正则表达式...

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

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

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

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


3
@ajsie:如果要对结果执行替换操作,则传统(捕获)组最有用。这是一个示例,其中我使用逗号分隔的姓氏和名字,然后颠倒它们的顺序(由于命名组)... regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
史蒂夫·

2
不,不一样。
里卡多·诺尔德

4
可能还会指出,使用正则表达式作为拆分定界符时,非捕获组非常有用:“ Alice and Bob” -split“ \ s +(?: and | or)\ s +”
Yevgeniy 2014年

7
在非捕获组(?:)之间进行区别,并解释先行断言和后向断言(?=,?!),这将是很有趣的。我刚刚开始学习正则表达式,但是据我了解,非捕获组用于匹配并“返回”它们匹配的内容,但是“返回值”并未“存储”用于反向引用。另一方面,向前和向后断言不仅不是“存储”的,它们也不是匹配的一部分,它们只是断言某些东西将匹配,如果我没有记错的话,它们的“ match”值将被忽略。 (我大概是对的吗?)
基督教徒

5
[]是一组;[123]一次匹配集合中的任何字符;[^ 123]匹配一次不在集合内的任何内容;[^ / \ r \ n] +匹配一个/多个与/,\ r,\ n不同的字符。
里卡多·诺尔德

180

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

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

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

这将匹配格式为1、2、3 ...或1st,2nd,3rd ...的数字,但是它将仅捕获数字部分。


3
简洁,也许是这里最好的解释。
NelsonGon

106

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

一个示例就是匹配IP地址的内容:

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

请注意,我不在乎保存前3个八位位组,但是通过(?:...)分组,我可以缩短正则表达式而不会招致捕获和存储匹配项的开销。


38

它使该组不被捕获,这意味着与该组匹配的子字符串将不包括在捕获列表中。红宝石中的一个例子来说明不同之处:

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

为什么我们不能只在此处使用“ abc” .match(/.(.)./)。captures?
PRASANNA SARAF,

@PRASANNASARAF当然可以。该代码的目的是表明(?:)不会产生捕获,也不是演示的有用示例(?:)(?:)当您希望对子表达式进行分组时(例如,当您要将量词应用于非原子子表达式或想要限制的范围时|)很有用,但又不想捕获任何内容。
sepp2k

26

历史动向:

非捕获性基团的存在可以用括号来解释。

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

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

当您想使用括号而不捕获子表达式时,可以使用NON-CAPTURING GROUPS。在这个例子中(?:a|b)c


6
我想知道为什么。我认为“为什么”对于记住此信息至关重要。
JMI MADISON

22

让我用一个例子尝试一下:

正则表达式代码: (?: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行是dog。

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

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

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

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

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

在此处输入图片说明


14

这组拍摄,你可以稍后在使用正则表达式匹配或者你可以在正则表达式的替换零件使用它们。建立一个非捕获组只是出于以下两个原因之一而使该组免于使用。

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

那几乎就是它们存在的原因。在学习组的同时,了解原子组,它们可以发挥很多作用!也有环视组,但它们稍微复杂些,使用量很少。

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

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

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

稍后在正则表达式\1中表示这将仅匹配第一个组(该([A-Z][A-Z0-9]*)组)中的相同文本(在这种情况下,它匹配结束标签)。


您能否举一个简单的示例,说明以后将如何使用它来匹配OR?
never_had_a_name

我的意思是您以后可以用来匹配,也可以在替换中使用它。该句子中的或只是为了向您展示捕获团体的两种用途
Bob Fincheimer 2010年

9

好吧,我是一名JavaScript开发人员,将尝试解释其与JavaScript有关的重要性。

考虑一种情况,cat is animal 当您想要匹配猫和动物时,您要匹配,并且两者is之间都应有一个“ in”。

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

7

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


7

我不能在最上面的答案上发表这样的评论:我想添加一个仅在最上面的答案中暗示的明确点:

非捕获组(?...) 不会从原始完全匹配中删除任何字符,它只会以可视化方式将正则表达式重新组织给程序员。

要访问正则表达式的特定部分而没有定义多余的字符,则始终需要使用 .group(<index>)


2
您提供了其余答案中最重要的提示。我尝试了其中的所有示例,并使用了最合适的专有名词,因为没有得到理想的结果。只有您的帖子显示了我出了错。
Seshadri R

很高兴听见!
Scott Anderson

6

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

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

现在说,您只需要地址的id部分。您要做的是获取匹配结果的第一组,用()正则表达式将其括起来,而方法是使用非捕获组语法,即?:。因此,正则表达式(\p{Alpha}*[a-z])(?:@example.com)将仅返回电子邮件的id部分。


5

我遇到的一件有趣的事情是,您可以在一个非捕获组中包含一个捕获组。在下面的正则表达式中查找匹配的网址:

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));

在此处输入图片说明

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

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


2

打开您的Google Chrome devTools,然后打开“控制台”标签:并输入以下内容:

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

运行它,您将看到:

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

JavaScript正则表达式引擎捕获三组,指标1,2,3项。现在使用非捕获标记来查看结果。

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

结果是:

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

这显然是非捕获组。


2

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

$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,请(?:)使用。

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

因此,输出将如下所示:

Fred wants a burger

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


1

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

所以正则表达式是

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

就这么简单。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.