(.)+
并且字符串"abcd"
。
在其他所有正则表达式中,捕获组1
只会产生一个结果:d
(请注意,完整匹配当然是预期的abcd
)。这是因为每次新使用捕获组都会覆盖先前的捕获。
另一方面,.NET会将它们全部记住。而且它是以堆栈的形式记忆的。在匹配上述正则表达式后,像
Match m = new Regex(@"(.)+").Match("abcd");
m.Groups[1].Captures
这是一个 CaptureCollection
,其元素对应于四个捕获结果。
0: "a"
1: "b"
2: "c"
3: "d"
这里的数字是对CaptureCollection
的索引。因此,每次组被再次使用时,都会将一个新的捕获推送到堆栈上。
如果我们使用命名捕获组,情况会变得更有趣。由于.NET允许重复使用相同的名称,因此我们可以编写以下正则表达式:
(?<word>\w+)\W+(?<word>\w+)
把两个单词捕获到同一组中。每次遇到一个带有特定名称的组,就会将其捕获推送到其堆栈上。因此,将此正则表达式应用于输入"foo bar"
并进行检查。
m.Groups["word"].Captures
0: "foo"
1: "bar"
CaptureCollection
中。但是我说过,这个集合是一个堆栈。那么我们可以从其中弹出东西吗?
事实证明我们可以。如果我们使用像(?<-word>...)
这样的组,则在子表达式...
匹配时,最后一个捕获将从堆栈word
中弹出。因此,如果我们将先前的表达式更改为
(?<word>\w+)\W+(?<-word>\w+)
CaptureCollection
。当然,这个示例并没有什么用处。
但是,减号语法还有一个细节:如果堆栈已经为空,则该组失败(无论其子模式如何)。我们可以利用这种行为来计算嵌套级别 - 这就是名称平衡组的由来(以及它变得有趣的地方)。假设我们想匹配正确括号的字符串。我们将每个开括号推入堆栈,并为每个闭括号弹出一个捕获。如果我们遇到了太多的闭括号,它将尝试弹出一个空堆栈并导致模式失败:^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
在重复中,我们有三种选择。第一种选择消耗所有不是括号的字符。第二个选择匹配包含 (
的内容,并将其推送到堆栈中。第三个选择匹配包含 )
的内容,并从堆栈中弹出元素(如果可能)。
注意:澄清一下,我们只检查未匹配的括号是否存在!这意味着不包含任何括号的字符串仍然会匹配,因为它们在某些语法中仍然是语法上有效的(其中需要括号匹配)。如果要确保至少有一个圆括号,请在^
后面添加前瞻(?=.*[(])
。
但是,这种模式并不完美(或完全正确)。
还有一个问题:这不能保证字符串末尾堆栈为空(因此 (foo(bar)
也是有效的)。.NET(和许多其他风格)有一个更多的结构来帮助我们:条件模式。一般语法如下:
(?(condition)truePattern|falsePattern)
falsePattern
是可选的,如果省略,则false-case始终匹配。条件可以是模式,也可以是捕获组的名称。在此我将重点介绍后一种情况。 如果它是捕获组的名称,则仅当该特定组的捕获堆栈不为空时才使用truePattern
。也就是说,像(?(name)yes|no)
这样的条件模式读作“如果name
已匹配并捕获了某些内容(仍然在堆栈上),则使用模式yes
,否则使用模式no
”。
因此,在我们上面的模式末尾,我们可以添加类似于(?(Open)failPattern)
的内容,如果Open
-堆栈不为空,则导致整个模式失败。使模式无条件失败的最简单方法是使用(?!)
(空的负向先行断言)。因此,我们有了最终的模式:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
(?<A-B>...)
语法是什么?(?<A-B>subPattern)
,不仅从堆栈 B
中弹出一个捕获,而且还将从弹出的 B
捕获到当前组之间的所有内容推送到堆栈 A
上。因此,如果我们对于闭合括号使用这样的组,同时从我们的堆栈中弹出嵌套级别,我们也可以将该对内容推送到另一个堆栈上:^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi在他的答案中提供了这个Live-Demo。
因此,我们可以将所有这些内容结合起来:
所有这些都可以在单个正则表达式中完成。如果这不令人兴奋... ;)
我第一次学习它们时发现有用的一些资源:
对M. Buettner卓越答案的一个小补充:
(?<A-B>)
语法是什么意思?(?<A-B>x)
与(?<-A>(?<B>x))
微妙地不同,它们产生相同的控制流程*,但它们的捕获方式不同。
例如,让我们看一个匹配平衡括号的模式:
(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))
比赛结束时,我们确实有一个平衡的字符串,但那就是全部——因为 B
堆栈为空,所以我们不知道括号在哪里。引擎为我们完成的努力已经化为乌有。
(Regex Storm 上的示例)
(?<A-B>x)
就是解决这个问题的方案。怎么做呢?它不会将 x
捕获到 $A
中:它捕获的是在上次捕获 B
和当前位置之间的内容。
让我们在模式中使用它:
(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))
这将捕获每对大括号中的字符串(以及它们的位置),并在其路径上进行每次捕获,将结果存入$Content
中。{1 2 {3} {4 5 {6}} 7}
,会有四个捕获:3
,6
,4 5 {6}
和1 2 {3} {4 5 {6}} 7
- 比没有或}}}}
更好。table
选项卡并查看${Content}
,捕获)
实际上,它甚至可以在没有平衡的情况下使用:(?<A>).(.(?<Content-A>).)
即使它们被分组分隔开,也会捕获前两个字符。
(这里更常用的是前瞻,但并不总是可扩展的:它可能会重复您的逻辑。)
(?<A-B>)
是一个强大的功能 - 它可以精确控制您的捕获。在尝试从模式中获取更多信息时,请记住这一点。
|'[^']*'
:example。如果你还需要转义字符,这里有一个例子:(Regex for matching C# string literals)[https://dev59.com/71TTa4cB1Zd3GeqPqDBa#4953878]。 - Kobi