环视(一)
Regex Guru 天牛神作,尝试翻成中文,不过总觉得翻成中文后变得很水。
原文地址: http://www.regular-expressions.info/lookaround.html
顺序环视和逆序环视
Perl 5 引入了两个强大的正则结构:顺序环视和逆序环视,二者合称为环视,也叫做零长度断言。环视和行起始(结束)锚、词起始(结束)锚一样是零长度匹配 (zero-width matches)的。但与锚匹配位置不同,环视匹配的是字符,并且在匹配完成之后,匹配到的字符就被丢弃,而只返回匹配是否成功,这就是为何被称之为断言。因为匹配的字符会被丢弃,因此环视不会消费(consume)字符,而仅仅是判断是否发生了一次成功匹配。借助环视可以创建几乎不可能的、简洁的正则表达式。
肯定顺序环视和否定顺序环视
当需要匹配某字符接下来不是某字符的情况时就需要用到否定顺序环视。像“字符 q 后面跟一个除 u 以外的字符”这样的需求是办法用否定字符组(Negated Character Classes)来实现的,但否定顺序环视 q(?!u) 则可以实现这个需求。否定顺序环视结构由一对圆括号标识,开括号后面跟一个问号和感叹号,再后面就是表达式字符 u。
肯定顺序环视与否定顺序环视原理相同,q(?=u) 匹配字符 q 后面紧跟字符 u 的情况,不过 u 并不作为匹配结果的一部分。肯定顺序环视结果同样由一对圆括号标识,开括号后面跟一个问号,等于号则代替感叹号的位置,表明该顺序环视是“肯定”的。
在顺序环视中可以包括的任意正则表达式(逆序环视则并非如此),如果其中包括分组捕获,那么将会产生与分组捕获对应的反向引用,而环视本身并不会创建任何的反向引用,因而也不会导致反向引用计数地增加。在环视中创建分组捕获也是用圆括号把表达式括起来,像这样:(?=u(regex)),如果错误地将嵌套关系书写为 ((?=regex)) 以期望捕获整个环视匹配的结果是行不通的,因为当引擎试图存储匹配结果到反向引用时,内层的环视已经将匹配结果丢弃,因为反向引用到的值始终都只是空字符。
引擎细节
首先看引擎是如何将表达式 q(?!u) 应用到目标文本 Iraq 的。第 1 个表达式字符是字面量 q,如你所知,这将使引擎发现目标文本中最靠左的 q 字符,q 是目标文本 Iraq 中最后一个字符,当前表达式字符在前推以后就变成空字符。接下来引擎注意到表达式中的顺序环视结构,就直接让环视中的 u 参与匹配,u 与空字符匹配失败,引擎记录下环视中的表达式匹配失败后发现这是个否定顺序环视,因而意味着环视中的表达式匹配失败,环视本身就匹配成功。表达式 q 和顺序环视均匹配成功,此时整个表达式也就匹配成功并返回匹配到的目标字符 q。
接着看看同样的表达式应用到目标文本 quit 的匹配过程。q 匹配 q,然后是环视中表达式 u 和目标文本中的 u 进行比较,匹配成功,环视中的表达式结束,接着引擎前进到目标文本中的字符 i,并记下环视中的表达式匹配成功然后丢弃环匹配结果,于是引擎又从目标文本中的 i 回溯到 u。
因为这是个否定顺序环视,环视中的表达式匹配成功结果导致环视本身匹配失败,而整个表达式的结束导致引擎不得不重新开始新一轮匹配,q 和 u 比较,匹配失败,q 和 i 比较,匹配失败,q 和 t 比较,还是匹配失败,目标文本结束。最终引擎报告整个表达式匹配失败。
再来一个稍复杂的例子,以确保你真的理解了顺序环视的含义。将 q(?=u)i 应用到 quit 的过程中,q 匹配 q,u 匹配 u,之后环视匹配成功,其中匹配到的 u 被再次丢弃,而引擎则从目标文本中的 i 回溯到 u,匹配继续,表达式中的 i 和目标文本中的 u 匹配失败,目标文本结束,于是引擎又从目标文本中的 u 开始新一轮匹配,最后整个表达式还是匹配失败,因为目标文本中已经没有字符 q 能和表达式中的 q 成功匹配了。
[...] 这里隆重推出Anrs同学翻译的教程: 环视一以及环视二。仔细阅读这两文章,彻底明白环视这两个概念,将会提升您的正则表达式功力。后文将建立在您已经理解环视这个概念的基础上。 [...]
小议“排除型匹配” | 我爱正则表达式
24 May 10 at 3:47 am