非捕获组,即,如何(?:)
在正则表达式中使用,它们有什么用?
18 回答
让我试着用一个例子来解释这一点。
考虑以下文本:
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/,它提供了大量关于正则表达式如何工作的详细信息;它还提供了一些正则表达式引擎可供选择。
您可以使用捕获组来组织和解析表达式。非捕获组具有第一个好处,但没有第二个好处。例如,您仍然可以说非捕获组是可选的。
假设您要匹配数字文本,但某些数字可以写为 1st, 2nd, 3rd, 4th,... 如果您想捕获数字部分,而不是(可选)后缀,您可以使用非捕获组.
([0-9]+)(?:st|nd|rd|th)?
这将匹配 1、2、3... 形式或 1st、2nd、3rd... 形式的数字,但它只会捕获数字部分。
?:
当您想要对表达式进行分组时使用,但您不想将其保存为字符串的匹配/捕获部分。
一个例子是匹配一个 IP 地址:
/(?:\d{1,3}\.){3}\d{1,3}/
请注意,我不关心保存前 3 个八位字节,但(?:...)
分组允许我缩短正则表达式,而不会产生捕获和存储匹配项的开销。
历史动机:
非捕获组的存在可以用括号来解释。
考虑表达式(a|b)c
和a|bc
,由于连接的优先级高于|
,这些表达式分别代表两种不同的语言 ({ac, bc}
和{a, bc}
)。
但是,括号也用作匹配组(如其他答案所述......)。
当您想要有括号但不捕获子表达式时,您使用非捕获组。在示例中,(?:a|b)c
它使组不捕获,这意味着与该组匹配的子字符串不会包含在捕获列表中。ruby 中的一个示例来说明差异:
"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
让我用一个例子来试试这个:
正则表达式代码:(?: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)
以后不能在代码中调用。
希望这解释了非捕获组的使用。
捕获的组可以稍后在正则表达式中使用以匹配,或者您可以在正则表达式的替换部分中使用它们。建立一个非捕获组只是使该组免于出于这些原因中的任何一个使用。
如果您尝试捕获许多不同的事物并且您不想捕获某些组,则非捕获组非常有用。
这几乎就是它们存在的原因。在学习组时,请了解原子组,它们会做很多事情!也有环视组,但它们稍微复杂一些,使用不多。
稍后在正则表达式中使用的示例(反向引用):
<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1>
[ 找到一个 xml 标签(没有 ns 支持) ]
([A-Z][A-Z0-9]*)
是一个捕获组(在这种情况下它是标记名)
稍后在正则表达式中\1
这意味着它只会匹配第一组(([A-Z][A-Z0-9]*)
组)中的相同文本(在这种情况下它匹配结束标记)。
tl;dr非捕获组,顾名思义,是您不希望包含在匹配中的正则表达式部分,?:
是一种将组定义为非捕获的方法。
假设您有一个电子邮件地址example@example.com
。以下正则表达式将创建两个组,即 id 部分和 @example.com 部分。(\p{Alpha}*[a-z])(@example.com)
. @
为简单起见,我们将提取包括字符在内的整个域名。
现在假设,您只需要地址的 id 部分。您想要做的是获取匹配结果的第一组,被()
正则表达式包围,这样做的方法是使用非捕获组语法,即?:
. 所以正则表达式(\p{Alpha}*[a-z])(?:@example.com)
将只返回电子邮件的 id 部分。
我无法评论最重要的答案:我想添加一个仅在最重要的答案中隐含的明确观点:
非捕获组(?...)
不会从原始完全匹配中删除任何字符,它只会对程序员重新组织正则表达式。
要访问没有定义无关字符的正则表达式的特定部分,您总是需要使用.group(<index>)
好吧,我是一名 JavaScript 开发人员,并将尝试解释其与 JavaScript 相关的意义。
考虑一个场景,cat is animal
当你想要匹配 cat 和 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"]
在复杂的正则表达式中,您可能会遇到希望使用大量组的情况,其中一些用于重复匹配,而另一些用于提供反向引用。默认情况下,匹配每个组的文本被加载到反向引用数组中。当我们有很多组并且只需要能够从反向引用数组中引用其中的一些时,我们可以覆盖此默认行为以告诉正则表达式某些组仅用于重复处理并且不需要捕获和存储在反向引用数组中。
我遇到的一件有趣的事情是,您可以在非捕获组中拥有一个捕获组。查看下面的正则表达式以匹配 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));
我想如果第一组(?:([A-Za-z]+):)
是非捕获组,那么为什么它http
在输出数组中返回字符串。
因此,如果您注意到([A-Za-z]+)
非捕获组内有一个嵌套组。该嵌套组在非捕获组中本身([A-Za-z]+)
就是一个捕获组(一开始没有) 。这就是为什么文本仍然被捕获但在非捕获组内但在捕获组之外的冒号字符不会在输出数组中报告的原因。?:
(?:([A-Za-z]+):)
http
:
一个简单的答案
使用它们来确保此处出现几种可能性之一(?:one|two)
或可选短语camp(?:site)?
或一般而言,您想要引用组/短语/部分的任何地方,而无需专门引用它。
他们将您捕获的组数保持在最低限度。
我想我会给你答案。不要在未检查匹配是否成功的情况下使用捕获变量。
除非匹配成功,否则捕获变量$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";
}
在上面的示例中,为了避免在 中捕获 bronto $1
,(?:)
使用了。
如果模式匹配,$1
则被捕获为下一个分组模式。
因此,输出将如下所示:
Fred wants a burger
如果您不想保存匹配项,这很有用。
它非常简单,我们可以通过简单的日期示例来理解,假设如果提到日期为 2019 年 1 月 1 日或 2019 年 5 月 2 日或任何其他日期,我们只是想将其转换为dd/mm/yyyy格式,我们不需要月份的名称是一月或二月,因此为了捕获数字部分,而不是(可选)后缀,您可以使用非捕获组。
所以正则表达式是
([0-9]+)(?:January|February)?
就这么简单。
打开您的 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]
这很明显什么是非捕获组。
(?: ... ) 充当组 ( ... ) 但不捕获匹配的数据。它确实比标准捕获组更有效。当您想要对某些内容进行分组但以后不需要重用它时使用它。@托托
让我举个地理坐标的例子,下面匹配两组
Latitude,Longitude
([+-]?\d+(?:\.\d+)?),([+-]?\d+(?:\.\d+)?)
让我们拿一个([+-]?\d+(?:\.\d+)?)
坐标可以是整数58
,也可以是因此,提到58.666
了可选的 ( .666
) 第二部分。(\.\d+)?
(...)? - for optional
但它是括号,那将是另一组比赛。而且我们不想要两个匹配一个 for58
另一个 for .666
,我们需要一个纬度作为匹配。非捕获组来了(?:)
与非捕获组[+-]?\d+(?:\.\d+)?
,58.666 和 58 都是单匹配