为什么您的测试可能有效(以及为什么不应该)
您能够在测试中匹配Iraq 的原因可能是您的字符串末尾包含\n(例如,如果您从shell 读取它)。如果您有一个以q 结尾的字符串,那么q[^u] 不能像其他人所说的那样匹配它,因为[^u] 匹配一个非u 字符-但关键是必须有一个字符。
我们实际上需要环视什么?
显然,在上述情况下,前瞻并不重要。您可以使用q(?:[^u]|$) 解决此问题。因此,我们仅在 q 后跟非u 字符或字符串结尾时才匹配。不过,前瞻还有更复杂的用途,如果您在没有前瞻的情况下使用它们,这将变得很痛苦。
此答案试图概述一些重要的标准情况,这些情况最好通过环视来解决。
让我们从查看带引号的字符串开始。匹配它们的常用方法是使用"[^"]*" 之类的东西(not 与".*?")。在开头的" 之后,我们只需重复尽可能多的非引号字符,然后匹配结束引号。再一次,一个否定的字符类是非常好的。但是在某些情况下,否定的字符类不会削减它:
多字符分隔符
现在如果我们没有双引号来分隔我们感兴趣的子字符串,而是一个多字符分隔符。例如,我们正在寻找---sometext---,其中sometext 内允许单- 和双-。现在你不能只使用[^-]*,因为那会禁止单个-。标准技术是在每个位置使用负前瞻,并且只使用下一个字符,如果它不是--- 的开头。像这样:
---(?:(?!---).)*---
如果您以前没有见过,这可能看起来有点复杂,但它肯定比其他替代方案更好(而且通常更有效)。
不同的分隔符
您会遇到类似的情况,您的分隔符只有一个字符,但可能是两个(或更多)不同字符之一。例如,在我们最初的示例中,我们希望允许单引号和双引号字符串。当然,您可以使用'[^']*'|"[^"]*",但最好在没有替代方案的情况下处理这两种情况。使用反向引用可以轻松处理周围的引号:(['"])[^'"]*\1。这可以确保匹配以它开始的相同字符结束。但是现在我们的限制太严格了——我们希望" 在单引号中,' 在双引号中。 [^\1] 之类的东西不起作用,因为反向引用通常包含多个字符。所以我们使用与上面相同的技术:
(['"])(?:(?!\1).)*\1
即在开头引号之后,在使用每个字符之前,我们确保它与开头字符不同。我们尽可能长时间地这样做,然后再次匹配开始字符。
重叠匹配
这是一个(完全不同的)问题,如果不进行环顾,通常根本无法解决。如果您在全局范围内搜索匹配项(或想要在全局范围内进行正则表达式替换),您可能已经注意到匹配项永远不会重叠。 IE。如果你在abcdefghi 中搜索...,你会得到abc、def、ghi 而不是bcd、cde 等等。如果你想确保你的匹配在其他东西之前(或包围),这可能是个问题。
假设你有一个类似的 CSV 文件
aaa,111,bbb,222,333,ccc
并且您只想提取完全是数字的字段。为简单起见,我假设任何地方都没有前导或尾随空格。如果没有环视,我们可能会进行捕获并尝试:
(?:^|,)(\d+)(?:,|$)
所以我们确保我们有一个字段的开头(字符串开头或,),然后只有数字,然后是字段的结尾(, 或字符串结尾)。在这之间,我们将数字捕获到1 组中。不幸的是,这不会在上面的例子中给我们333,因为它之前的,已经是匹配,222,的一部分——并且匹配不能重叠。环顾四周解决问题:
(?<=^|,)\d+(?=,|$)
或者如果你更喜欢双重否定而不是交替,这相当于
(?<![^,])\d+(?![^,])
除了能够获取所有匹配项之外,我们还摆脱了通常可以提高性能的捕获。 (感谢 Adrian Pronk 提供的示例。)
多个独立条件
另一个使用lookarounds(特别是lookaheads)的经典例子是当我们想要同时检查输入的多个条件时。假设我们要编写一个正则表达式,以确保我们的输入包含一个数字、一个小写字母、一个大写字母、一个不是这些字符的字符,并且没有空格(例如,为了密码安全)。如果没有环视,您必须考虑数字、小写/大写字母和符号的所有排列。喜欢:
\S*\d\S*[a-z]\S*[A-Z]\S*[^0-9a-zA_Z]\S*|\S*\d\S*[A-Z]\S*[a-z]\S*[^0-9a-zA_Z]\S*|...
这些只是 24 种必要排列中的两种。如果您还想确保同一正则表达式中的最小字符串长度,则必须将它们分布在 \S* 的所有可能组合中 - 在单个正则表达式中根本不可能做到。
期待救援!我们可以简单地在字符串的开头使用几个前瞻来检查所有这些条件:
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^0-9a-zA-Z])(?!.*\s)
因为前瞻实际上不消耗任何东西,所以在检查每个条件后,引擎会重置到字符串的开头,并可以开始查看下一个条件。如果我们想添加一个最小字符串长度(比如8),我们可以简单地追加(?=.{8})。更简单、更易读、更易于维护。
重要提示:这不是在任何实际环境中检查这些条件的最佳通用方法。如果您以编程方式进行检查,通常最好为每个条件使用一个正则表达式,并分别检查它们 - 这让您返回更有用的错误消息。但是,如果您有一些固定的框架允许您仅通过提供单个正则表达式来进行验证,则上述内容有时是必要的。此外,如果您有独立的字符串匹配标准,则值得了解一般技术。
我希望这些示例能让您更好地了解人们为什么喜欢使用环视。还有更多的应用程序(另一个经典是inserting commas into numbers),但重要的是您要意识到(?!u) 和[^u] 之间存在差异,并且在某些情况下否定字符类根本不够强大。