如何在正则表达式中使用非捕获组,即(?:),它们有什么好处?
当前回答
让我用一个例子来解释一下。
考虑以下文本:
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(请注意,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/,提供了有关正则表达式工作方式的大量详细信息;它还提供了一些正则表达式引擎供选择。
其他回答
让我用一个例子来解释一下。
考虑以下文本:
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(请注意,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/,提供了有关正则表达式工作方式的大量详细信息;它还提供了一些正则表达式引擎供选择。
?: 当您想对表达式进行分组,但不想将其保存为字符串的匹配/捕获部分时,使用。
一个例子是匹配IP地址:
/(?:\d{1,3}\.){3}\d{1,3}/
请注意,我不在乎保存前3个八位字节,但(?:…)分组允许我缩短正则表达式,而不会导致捕获和存储匹配项的开销。
为了补充本主题中的其他好答案,我想添加一个有趣的观察结果。
查找:您可以在非捕获组中包含捕获组。
问题详细信息:请查看下面匹配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));
我可以看到返回数组的第一个索引包含字符串http(请参阅屏幕截图)。
此时,我的想法是http和冒号:两者都不会在输出中报告,因为它们位于非捕获组中。如果第一个正则表达式组(?:([A-Za-z]+):)是非捕获组,那么为什么它在输出数组中返回http字符串?
解释:如果你注意到,([A-Za-z]+)是一个捕获组(开头没有?)。但这个捕获组本身位于一个非捕获组(?:([a-Za-z]+):)内,后面跟着一个:字符。这就是为什么文本http仍然被捕获,但冒号:字符落在非捕获组内(但在捕获组外),不会在输出数组中报告。
历史动机:
非捕获组的存在可以用括号来解释。
考虑表达式(a|b)c和a|bc,由于串联优先于|,这些表达式分别表示两种不同的语言({ac,bc}和{a,bc})。
然而,括号也用作匹配组(如其他答案所解释的…)。
当您想有括号但不想捕获子表达式时,可以使用NON-CAPTURING GROUPS。在示例中,(?:a|b)c
我是一名JavaScript开发人员,将尝试解释其与JavaScript相关的意义。
考虑一个场景,你想将猫与动物相匹配当你想要匹配猫和动物时,两者之间应该有一个平衡点。
// 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"]