首页 文章

什么是非捕获组? (?:)做什么?

提问于
浏览
1421

如何使用 ?: 以及它有什么用?

14 回答

  • 138

    它使组不捕获,这意味着该组匹配的子字符串不会包含在捕获列表中 . ruby中的一个例子来说明差异:

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

    如果要对表达式进行分组,则使用 ?: ,但不希望将其保存为字符串的匹配/捕获部分 .

    一个例子是匹配IP地址:

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

    请注意,我不关心保存前3个八位字节,但是 (?:...) 分组允许我缩短正则表达式而不会产生捕获和存储匹配的开销 .

  • 6

    历史动机:可以使用括号来解释非捕获组的存在 . 考虑表达式(a | b)c和a | bc,由于串联的优先级超过|,这些表达式分别代表两种不同的语言({ac,bc}和{a,bc}) . 但是,括号也用作匹配组(如其他答案所解释的那样) .

    如果要使用括号但不捕获子表达式,则使用非捕获组 . 在示例中,(?:a | b)c

  • 93

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

  • 3

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

    考虑一个场景,你想要匹配 cat is 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"]
    
  • 15

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

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

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

    这将匹配形式1,2,3 ......或者形式为1st,2nd,3rd,......但它只会捕获数字部分 .

  • 13

    让我试着用一个例子解释一下 .

    请考虑以下文本:

    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 may not be the best tool for the job,但它很好作为一个例子) . 你想解析标签,所以你可以做这样的事情(我添加了空格,以便更容易理解):

    \<(?<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/,它提供了有关正则表达式如何工作的大量细节;它还提供了一些正则表达式引擎可供选择 .

  • 1942

    您可以在正则表达式中使用 capture 以匹配 OR 的组,您可以在替换部件中使用它们正则表达式制作一个 non-capturing 组只会使该组免于被用于上述任何一个原因 .

    如果您尝试捕获许多不同的东西并且有一些您不想捕获的组,则非捕获组很棒 .

    这就是它们存在的原因 . 在您了解群组的同时,了解Atomic Groups,他们做了很多!还有外观组,但它们有点复杂,并没有那么多使用 .

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

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

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

    后面的正则表达式是 \1 ,这意味着它只匹配第一组中的相同文本( ([A-Z][A-Z0-9]*) 组)(在这种情况下,它匹配结束标记) .

  • 4

    让我试试这个例子: -

    正则代码: - (?: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组并继续..

    但是通过给出?:我们使匹配组不被捕获(在匹配的组中不计数,因此分组编号从第一个捕获的组开始而不是非捕获的组),以便重复匹配的结果-group(?:animal)以后不能在代码中调用 .

    希望这能解释非捕获组的使用 .

    enter image description here

  • 0

    我遇到的一件有趣的事情是,您可以在非捕获组中拥有捕获组 . 请查看以下正则表达式以匹配网址:

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

    enter image description here

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

    因此,如果您注意到非捕获组中存在嵌套组 ([A-Za-z]+) . 嵌套组 ([A-Za-z]+) 本身是非捕获组 (?:([A-Za-z]+):) 内的捕获组(开头没有 ?: ) . 这就是为什么文本 http 仍然被捕获但是在非捕获组内但在捕获组外部的冒号 : 字符未在输出数组中报告 .

  • 1

    我无法对最佳答案发表评论:我想补充一个明确的观点,这只是在最顶层的答案中暗示的:

    非捕获组 (?...) 从原始完全匹配中执行 not remove 任何字符, it only 可视化地将正则表达式重组为程序员 .

    要访问正则表达式的特定部分而没有定义无关的字符,您总是需要使用 .group(<index>)

  • 5

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

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

    现在让我们说,你只需要地址的id部分 . 你想要做的是获取匹配结果的第一组,在正则表达式中被 () 包围,并且这样做的方法是使用非捕获组语法,即 ?: . 所以正则表达式 (\p{Alpha}*[a-z])(?:@example.com) 将只返回电子邮件的id部分 .

  • 9

    打开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]
    

    这显然是什么是非捕获组 .

  • 3

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

    除非匹配成功,否则捕获变量$ 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
    

    如果您不想要匹配,这很有用得救

相关问题