非捕获组,即(?:)
在正则表达式中是如何使用的?它们有什么好处?
非捕获组,即(?:)
在正则表达式中是如何使用的?它们有什么好处?
让我用一个例子来解释。
考虑以下文本:
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/,它提供了很多关于正则表达式工作方式的详细信息;它还提供了几个可选择的正则表达式引擎。
(?:
的正则表达式比具有捕获组 '(' 的相同正则表达式快得多。因此,当我们不需要捕获组时,应该使用非捕获组。 - luke使用捕获组可以组织和解析表达式。非捕获组具有第一个好处,但不具备第二个的开销。例如,您仍然可以说非捕获组是可选的。
假设您想匹配数值文本,但某些数字可能以1st、2nd、3rd、4th等形式书写。如果您想捕获数字部分但不包括(可选的)后缀,则可以使用非捕获组。
([0-9]+)(?:st|nd|rd|th)?
这将匹配形式为1、2、3...或形式为1st、2nd、3rd等的数字,但它只会捕获数字部分。
([0-9]+)(st|nd|rd|th)?
?有了\1
,我就有了数字,不需要?:
。顺便问一下,末尾的?
是什么意思? - Timo?
表示捕获组是可选的。 - Pillager225?:
用于分组表达式,但不想将其保存为字符串中匹配/捕获的部分。
例如,可以使用它来匹配IP地址:
/(?:\d{1,3}\.){3}\d{1,3}/
请注意,我不关心保存前三个八位组,但是(?:...)
分组允许我缩短正则表达式而不会产生捕获和存储匹配的开销。
历史背景:
非捕获组的存在可以通过使用括号来解释。
考虑表达式 (a|b)c
和 a|bc
,由于串联优先级高于 |
,这些表达式表示两种不同的语言(分别为 {ac, bc}
和 {a, bc}
)。
然而,括号也可以用作匹配组(就像其他答案中所解释的一样……)。
当您想要括号但不捕获子表达式时,您使用非捕获组。在本例中,使用(?:a|b)c
。
让我用一个例子来说明:
正则表达式代码: (?: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)
的结果。
希望这解释了非捕获组的用法。
(?:animal=)(\w+)(,)\1\2
因为 (?:animal)(?:=)
的匹配结果只有 animal=
,所以没有必要有两个非捕获组。另一方面,如果你想要捕获第一个单词,(animal)(?:=)(\w+)(,)\2\3
可以重复第一个动物和逗号进行相同的匹配。例如,如果你的文本还包括蔬菜和矿物质,你可以使用 (?:\w+)(?:=)(\w+)(,)\2\3
,然后你就可以将等于号前面的类别或者其他名字放在第一组中。在这种情况下,非捕获组将仅是 (?:=)
,它将被丢弃。 - Jeter-work这会使得这个组成为非捕获组,也就是说,与该组匹配的子字符串将不会被包含在捕获列表中。以下是 Ruby 的示例来说明差异:
"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
(?:)
不会产生捕获,而不是演示 (?:)
的有用示例。当您想要对非原子子表达式应用量词或者想要限制 |
的范围时,(?:)
是很有用的,但您不想捕获任何内容。 - sepp2k捕获组可以在正则表达式中后续使用来进行“或”匹配,或者用于替换操作。使用非捕获组可以将该组从上述两种情况中排除。
如果您尝试捕获多个不同的内容并且其中有一些组不想捕获,那么非捕获组就非常有用。
这基本上就是它们存在的原因。当您学习有关组的知识时,还应该学习原子组,它们可以做很多事情!还有查找组,但它们更加复杂,用得不太多。
以下是一个捕获组的后续使用示例(反向引用):
<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1>
[ 查找一个xml标签(不支持ns) ]
([A-Z][A-Z0-9]*)
是一个捕获组(在此示例中为标签名称)
正则表达式中后续出现的 \1
表示仅会匹配第一个组(即([A-Z][A-Z0-9]*)
组)中匹配到的相同文本(在此示例中,它是用来匹配结束标签)。
一个简单的答案
使用它们来确保发生几种可能性之一的地方 (?:one|two)
或可选短语 camp(?:site)?
,或者在一般情况下,您想要建立一个组/短语/部分而无需特别引用它。
它们可以将捕获的组数保持最少。
简述 非捕获组,顾名思义,是正则表达式中你不希望包含在匹配结果中的部分,?:
则是定义一个非捕获组的方法。
假设你有一个邮箱地址 example@example.com
。下面的正则表达式将会创建两个组,一个是id部分,另一个是@example.com部分。(\p{Alpha}*[a-z])(@example.com)
。为了简单起见,我们提取整个域名,包括@
字符。
现在假设,你只需要该地址的id部分。你需要做的就是获取匹配结果的第一个用括号()
包围的组,这时你就可以使用非捕获组的语法,即?:
。所以,这个正则表达式(\p{Alpha}*[a-z])(?:@example.com)
将只返回该邮箱地址的id部分。
(\w+)@\w.\w.*
,只捕获ID部分。但是,如果必须是example.com,并且您想要丢弃example.com,因为所有良好的匹配都有它,那么您将使用非捕获组。(\w+)@(?:example.com)
。 - Jeter-work