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