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


当前回答

打开您的Google Chrome devTools,然后单击Console选项卡:并键入以下内容:

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

这是显而易见的非捕获组。

其他回答

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

考虑以下文本:

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

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

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

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

这将匹配形式1、2、3…或形式1、第2、第3…的数字,。。。但它将只捕获数字部分。

简单的答案

使用它们来确保在这里出现几种可能性中的一种(?:一个|两个)或可选短语camp(?:站点)?或者一般来说,任何你想建立一个组/短语/部分而不需要特别提及的地方。

他们将您捕获的组数保持在最小值。

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

除非匹配成功,否则捕获变量$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中捕获bronto,使用了(?:)。

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

因此,输出如下:

Fred wants a burger

如果不希望保存匹配项,则此选项很有用。

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

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