正则表达式中的非捕获组是什么?

unhi4e5o  于 2021-07-29  发布在  Java
关注(0)|答案(14)|浏览(285)

非捕获组,即。 (?:) ,用于正则表达式,它们有什么好处?

pokxtpni

pokxtpni1#

让我试着用一个例子来解释这一点。
考虑以下文本:

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的主机和路径。所以,我将regex修改为包含非捕获组 (?:) .

(?: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\>

第一个正则表达式有一个命名组(标记),而第二个正则表达式使用一个公共组。两个正则表达式都做同样的事情:它们使用第一个组中的值(标记的名称)来匹配结束标记。区别在于第一个使用名称来匹配值,第二个使用组索引(从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} .
我建议你玩regexhttp://regex101.com/,它提供了关于regex如何工作的大量细节;它还提供了一些regex引擎供选择。

hvvq6cgz

hvvq6cgz2#

可以使用捕获组来组织和解析表达式。非捕获组有第一个好处,但没有第二个好处的开销。例如,您仍然可以说非捕获组是可选的。
假设你想匹配数字文本,但有些数字可以写为1,2,3,4,。。。如果要捕获数字部分,而不是(可选)后缀,则可以使用非捕获组。

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

匹配1,2,3。。。或者以第一、第二、第三,。。。但它只会捕获数字部分。

7gs2gvoe

7gs2gvoe3#

?: 当您希望对表达式进行分组,但不希望将其保存为字符串的匹配/捕获部分时,使用。
例如,与ip地址匹配的内容:

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

注意,我不关心保存前3个八位字节,但是 (?:...) 分组使我能够缩短regex,而不会产生捕获和存储匹配项的开销。

ulydmbyx

ulydmbyx4#

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

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

4ioopgfo5#

历史动机:
非捕获群的存在可以用括号来解释。
考虑一下这些表达式 (a|b)c 以及 a|bc ,因为级联的优先级高于 | ,这些表达式表示两种不同的语言( {ac, bc} 以及 {a, bc} 分别)。
但是,括号也用作匹配组(如其他答案所述…)。
如果希望有括号但不捕获子表达式,则使用非捕获组。在这个例子中, (?:a|b)c

zyfwsgd6

zyfwsgd66#

让我举个例子:
正则表达式代码: (?: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行为cat,第2行为cat,第3行为dog。 \2 -->捕获组2的结果,即逗号(,)
所以在这段代码中 \1 以及 \2 我们在代码后面分别回忆或重复捕获的组1和组2的结果。
按照代码顺序 (?:animal) 应该是第1组 (?:=) 应该是第2组,然后继续。。
但是通过给予 ?: 我们将匹配组设为非捕获组(在匹配组中不计数,所以分组编号从第一个捕获组开始,而不是从非捕获组开始),这样匹配组结果的重复性就降低了 (?:animal) 以后不能在代码中调用。
希望这能解释非捕获组的用法。

dgtucam1

dgtucam17#

捕获的组可以稍后在regex中使用以匹配,也可以在regex的替换部分中使用它们。创建一个非捕获组只是免除了出于以下原因使用该组。
如果您试图捕获许多不同的内容,但有些组您不想捕获,那么非捕获组是非常好的。
这就是他们存在的原因。当你学习组,学习原子组的时候,他们做了很多!也有lookaround组,但它们稍微复杂一点,使用不多。
稍后在regex中使用的示例(backreference): <([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [查找xml标记(不支持ns)] ([A-Z][A-Z0-9]*) 是捕获组(在本例中是标记名)
后来在正则表达式中是 \1 这意味着它将只匹配第一组中的相同文本(第二组) ([A-Z][A-Z0-9]*) 组)(在本例中,它与结束标记匹配)。

kupeojn6

kupeojn68#

热释光;dr非捕获组,顾名思义是regex中您不希望包含在匹配和 ?: 是一种将组定义为非捕获的方法。
假设你有一个电子邮件地址 example@example.com . 下面的regex将创建两个组,id部分和@example.com部分。 (\p{Alpha}*[a-z])(@example.com) . 为了简单起见,我们提取整个域名,包括 @ 性格。
现在假设,你只需要地址的id部分。你要做的是抓住比赛结果的第一组,周围是 () 在regex中,这样做的方法是使用非捕获组语法,即。 ?: . 所以正则表达式 (\p{Alpha}*[a-z])(?:@example.com) 将只返回电子邮件的id部分。

owfi6suc

owfi6suc9#

我无法对最重要的答案发表评论来说明这一点:我想补充一个明确的观点,这一点仅在最重要的答案中暗示:
非捕获组 (?...) 不会从原始的完全匹配中删除任何字符,它只会向程序员直观地重新组织正则表达式。
要访问regex的特定部分而不使用定义的无关字符,您必须始终使用 .group(<index>)

5ktev3wc

5ktev3wc10#

我是一名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"]
nkcskrwz

nkcskrwz11#

在复杂正则表达式中,可能会出现希望使用大量组的情况,其中一些组用于重复匹配,另一些组用于提供反向引用。默认情况下,匹配每个组的文本将加载到backreference数组中。当我们有很多组并且只需要从backreference数组中引用其中的一些组时,我们可以重写这个默认行为来告诉正则表达式某些组只用于重复处理,不需要捕获并存储在backreference数组中。

yv5phkfx

yv5phkfx12#

我遇到的一件有趣的事情是,在非捕获组中可以有一个捕获组。查看下面的正则表达式以获取匹配的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 当我这么想的时候 http 还有结肠 : 两者都不会被报告,因为它们在非捕获组中。

console.debug(parse_url_regex.exec(url));


我想如果第一组 (?:([A-Za-z]+):) 是一个不被捕获的群体,那它为什么要回来呢 http 输出数组中的字符串。
如果你注意到有一个嵌套的组 ([A-Za-z]+) 在非捕获组中。那个嵌套的组 ([A-Za-z]+) 是捕获组(没有 ?: 一开始)在一个非捕获组中 (?:([A-Za-z]+):) . 这就是为什么文本 http 还是会被抓到但是结肠 : 在非捕获组内但在捕获组外的字符不会在输出数组中报告。

ttcibm8c

ttcibm8c13#

我想我会给你答案的。在没有检查匹配是否成功之前,不要使用捕获变量。
捕获变量, $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 , (?:) 已使用。
如果模式匹配,那么 $1 作为下一个分组模式捕获。
因此,输出如下:

Fred wants a burger

如果您不想保存匹配项,那么它很有用。

vktxenjb

vktxenjb14#

打开google chrome开发工具,然后打开控制台选项卡:并键入以下内容:

"Peace".match(/(\w)(\w)(\w)/)

运行它,您将看到:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

这个 JavaScript regexp引擎捕获三个组,索引为1、2、3的项。现在使用非

相关问题