汉斯,我将诱饵充实我先前的回答。您说过您想要“更完整的东西”,所以我希望您不会介意冗长的答案,只是想取悦他人。让我们从一些背景开始。
首先,这是一个很好的问题。除某些上下文外(例如,在代码块内或括号内),经常会遇到有关匹配某些模式的问题。这些问题常常引起相当尴尬的解决方案。因此,您关于多个上下文的问题是一个特殊的挑战。
惊喜
令人惊讶的是,至少有一种有效的解决方案是通用的,易于实施的并且易于维护。它适用于所有正则表达式,可让您检查代码中的捕获组。它碰巧回答了一些常见的问题,这些问题最初听起来可能与您的听起来有所不同:“匹配除“甜甜圈”以外的所有单词”,“替换除...以外的所有单词”,“匹配除我母亲黑名单上的所有单词之外的所有单词”,“忽略”标签”,“匹配温度,除非用斜体显示” ...
可悲的是,该技术并不为人所知:我估计在二十个可以使用它的SO问题中,只有一个回答提到了它-这意味着可能有五十或六十个回答。在评论中查看我与Kobi的交流。该技术是在一些深入的描述本文这就要求它(乐观)“最好的正则表达式招过”。在不赘述的情况下,我将尝试为您牢牢掌握该技术的工作原理。有关更多详细信息和各种语言的代码示例,建议您查阅该资源。
更好的变化
使用特定于Perl和PHP的语法可以实现相同的变体。你会看到它所以在正则表达式等名家之手CasimiretHippolyte和哈姆扎。我将在下面向您详细介绍,但我的重点是适用于所有regex风格的通用解决方案(只要您可以检查代码中的捕获组)。
感谢所有背景,zx81 ...但是有什么秘诀?
关键事实
该方法返回第1组捕获中的匹配项。它根本不关心整体比赛。
实际上,诀窍是匹配我们不需要的各种上下文(使用|
OR /交替链接这些上下文)以“中和它们”。匹配所有不需要的背景之后,交替的最后部分匹配我们也希望它捕获到组1。
一般配方是
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
这将匹配Not_this_context
,但从某种意义上说,匹配会进入垃圾箱,因为我们不会查看整体匹配:我们只会查看第1组捕获。
对于您的情况,您可以忽略数字和三个上下文,我们可以这样做:
s1|s2|s3|(\b\d+\b)
请注意,因为我们实际上是将s1,s2和s3匹配,而不是尝试通过环视来避免它们,所以s1,s2和s3的各个表达式可以整天保持清晰。(它们是的每一边的子表达式|
)
整个表达式可以这样写:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
请参阅此演示(但请注意右下窗格中的捕获组。)
如果您在思维上尝试在每个|
定界符处拆分此正则表达式,则实际上它只是一系列四个非常简单的表达式。
对于支持自由调配的口味,这尤其好。
(?mx)
### s1: Match line that ends with a period ###
^.*\.$
| ### OR s2: Match anything between parentheses ###
\([^\)]*\)
| ### OR s3: Match any if(...//endif block ###
if\(.*?//endif
| ### OR capture digits to Group 1 ###
(\b\d+\b)
这非常容易阅读和维护。
扩展正则表达式
当您想忽略s4和s5的更多情况时,可以在左侧的更多交替中添加它们:
s4|s5|s1|s2|s3|(\b\d+\b)
这是如何运作的?
不需要的上下文会添加到左侧的替代列表中:它们会匹配,但是不会检查这些总体匹配,因此匹配它们是一种将它们放入“垃圾箱”的方法。
但是,您想要的内容将被捕获到组1中。然后,您必须以编程方式检查是否已设置组1而不是空的。这是一项琐碎的编程任务(稍后我们将讨论如何完成),尤其是考虑到它给您提供了一个简单的正则表达式,您可以一目了然,并根据需要进行修改或扩展。
我并不总是喜欢可视化,但是这很好地展示了该方法的简单性。每条“线”对应于一个潜在的匹配,但只有底线被捕获到组1中。
Debuggex演示
Perl / PCRE变化
与上面的一般解决方案相反,Perl和PCRE存在一个变体,至少在正则表达式神之手(如@CasimiretHippolyte和@HamZa)上经常出现在SO上。它是:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
在您的情况下:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
使用此变体要容易一些,因为只跳过了上下文s1,s2和s3中匹配的内容,因此您无需检查第1组捕获的内容(注意括号已消失)。比赛只包含whatYouWant
请注意(*F)
,(*FAIL)
和(?!)
都是一样的东西。如果您想变得更晦涩,可以使用(*SKIP)(?!)
此版本的演示
应用领域
这是该技术通常可以轻松解决的一些常见问题。您会注意到,选择这个词可能会使其中一些问题听起来有所不同,而实际上它们实际上是相同的。
- 如何匹配foo之类的标记之外的foo
<a stuff...>...</a>
?
- 除了
<i>
标签或JavaScript代码段(更多条件)外,如何匹配foo ?
- 如何匹配不在此黑名单上的所有单词?
- 如何忽略SUB ... END SUB块中的任何内容?
- 除了s1 s2 s3之外,我该如何匹配所有内容?
如何对第1组捕获进行编程
您不是要编写代码,而是要完成代码……检查第1组的代码显然取决于您选择的语言。无论如何,它不应在用于检查匹配项的代码中添加多行。
如有疑问,建议您查看代码示例部分前面提到的文章,其中提供了多种语言的代码。
备择方案
根据问题的复杂性以及所使用的正则表达式引擎,有几种选择。这是可以适用于大多数情况(包括多种情况)的两种情况。我认为,这两种方法都没有像s1|s2|s3|(whatYouWant)
配方,仅仅是因为清晰度总是会胜出。
1.替换然后匹配。
一个听起来不错但在许多环境中都能正常工作的好的解决方案是分两个步骤工作。第一个正则表达式通过替换潜在冲突的字符串来中和您要忽略的上下文。如果只想匹配,则可以用空字符串替换,然后在第二步中运行匹配。如果要替换,您可以先用与众不同的东西替换要忽略的字符串,例如用固定宽度的包围数字@@@
。替换之后,您可以随意替换您真正想要的内容,然后必须还原您独特的@@@
字符串。
2.环顾四周。
您的原始帖子显示,您了解如何使用环顾四周排除单个条件。您说过C#对此非常有用,您说的没错,但这不是唯一的选择。例如,在C#,VB.NET和Visual C ++中找到的.NET regex风格,以及在Python中仍要regex
替换的仍在实验中的模块re
,是我所知道的仅有的两个支持无限宽后向搜索的引擎。借助这些工具,在后方看一眼的情况不仅可以照顾比赛的后面,而且可以看比赛以及超越比赛,而无需协调前瞻。还有更多条件吗?更多环顾四周。
回收C#中用于s3的正则表达式,整个模式如下所示。
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
但是现在您知道我不推荐这样做了,对吧?
删除项
@HamZa和@Jerry建议我为试图删除的情况提到一个额外的技巧WhatYouWant
。您还记得要匹配的配方WhatYouWant
(将其捕获到第1组中)是s1|s2|s3|(WhatYouWant)
吗?要删除的所有实例WhatYouWant
,请将正则表达式更改为
(s1|s2|s3)|WhatYouWant
对于替换字符串,请使用$1
。在这里发生的是,对于s1|s2|s3
匹配的每个实例,替换$1
将用自身替换该实例(由引用$1
)。另一方面,当WhatYouWant
被匹配时,它被一个空组代替,别无其他,因此被删除。观看此演示,谢谢@HamZa和@Jerry提出了这一出色的建议。
替代品
这将带给我们替代品,我将在此简单介绍。
- 如果不进行任何替换,请参见上面的“删除”技巧。
- 替换时,如果使用Perl或PCRE,请使用
(*SKIP)(*F)
上面提到的变体完全匹配您想要的内容,然后进行直接替换。
- 在其他情况下,在替换函数调用中,使用回调或lambda检查匹配项,并替换是否设置了组1。如果您需要帮助,已经参考的文章将为您提供各种语言的代码。
玩得开心!
不,等等,还有更多!
啊,不,我将其保存在二十卷的回忆录中,这些回忆录将于明年春季发行。
\K
没有特殊的php语法。请详细说明您要说的话。如果您想告诉我们不需要“复杂”的解决方案,则必须说出什么对您来说很复杂以及为什么。