正则表达式零宽断言

正则表达式里”零宽断言”这个词可能有很多不同的说法, 比如环视, 正向匹配, 反向匹配, 零宽度负预测先行断言…

[su_table]

正向/预测先行/顺序/
从左到右/pattern的前面位置
负向/回顾后发/逆序/
从右到左/pattern的后面位置
肯定/正 (?=pattern) (?<=pattern)
否定/负 (?!pattern) (?<!pattern)

[/su_table]

书写规则是肯定用”=”, 否定用”!”(和一般编程语言类似); 顺序不加”<“, 逆序在”?”后加”<“.

“零宽” 意思是零宽度, 就是说此(?=pattern)匹配的并不是几个字符, 而是一个”位置”或者说”长度为0的字符”, 比如对于字符串”233″, (?<=2)匹配结果是”2<这个位置>33″. 与常见的匹配开头位置的”^”, 匹配结尾位置的”$”是类似的. “^”和”$”匹配到的就是”位置”而不是实际的几个字符. 本来一个子字符串可以用两个值来确定, 比如开头位置&结尾位置, 零宽的情况就是开头位置=结尾位置, 虽然信息量变少了, 但是开头(结尾)位置这一信息还是存在的.
“断言”意思是要求判断为真, 就是说它是用来做判断的而不是实际内容.

考虑用以下正则表达式匹配字符串”ABAC”:

AB
匹配紧接着的字符”A”及其后的字符”B”, 共1+1=2个字符, 结果是 “AB”(前两个字符)

A(?=B)
匹配紧接着的字符”A”及其后的 字符”B”的前面的位置, 共1+0=1个字符, 结果是第一个”A”以及 第一个”A”及”B”前面的位置, 所以也就是第一个”A”. 而第二个”A”是无法被匹配到的, 因为第二个”A”之后紧接着的是 字符”C”前面的位置, 不符合”(?=B)”

A(?!B)
匹配紧接着的字符”A”及其后的 不是字符”B”的前面的位置, 共1+0个字符, 结果是第二个”A”以及 第二个”A”及”C”前面的位置, 所以也就是第二个”A”. 而第一个”A”是无法被匹配到的, 因为第一个”A”之后紧接着的是字符”B”前面的位置, 不符合”(?=B)”

(?<=B)A
匹配紧接着的 字符”B”后面的位置 及其后的”A”, 共0+1=1个字符, 结果是字符”B”后面的位置 以及第二个”A”, 所以也就是第二个”A”. 而第一个”A”是无法被匹配到的, 因为第一个”A”之前不是 字符”B”后面的位置, 不符合”(?<=B)”

(?<!B)A
匹配紧接着的 不是字符”B”后面的位置 及其后的”A”, 共0+1=1个字符, 结果是字符串一开头的位置 以及第一个”A”, 所以也就是第一个”A”. 而第二个”A”是无法被匹配到的, 因为第二个”A”之前是 字符”B”后面的位置, 不符合”(?<!B)”

(?=B)A
匹配紧接着的 字符”B”前面的位置 及其后的”A”, 共0+1=1个字符. 结果… 咦? 字符”B”前面的位置 之后紧接着的不一定是”B”吗, 怎么可能是”A”? 所以这个正则表达式什么都匹配不到.
不过这个可能并不是没用的, 如果把A改成”.”之类的符号就可能有结果了, 比如
(?=Python|Java).
匹配的是”Python”或”Java”的第一个字母”P”或”J”(长度1字符)

(?!B)A
匹配紧接着的 不是字符”B”的前面的位置 及其后的”A”, 共0+1=1个字符. 结果… 咦? 字符”A”前面一定 不是字符”B”前面的位置 啊, (?!B)一定是符合的, 有什么用? 所以这个正则表达式可以匹配任意的”A”, 与正则表达式”A”效果完全一样.
不过这个可能并不是没用的, 如果把A改成”.”之类的符号可能就有结果了, 比如
(?!B).
匹配的是 不是字符”B”的前面的位置, 及其后的任意字符, 共0+1=1个字符. 结果就是任意一个不是”B”的字符. 这与正则表达式[^B]的效果是完全一样.
区别在于, 零宽断言里是表达式而不是单纯的字符(串), 比如
[^AB]
匹配的是 不是字符”A”或”B”的字符, 用其匹配”ABC”的结果是”C”
(?!AB).
匹配的是 不是字符串”AB”的前面的位置, 及其后的任意1个字符. 用其匹配”ABC”的结果是”B”和”C”(两个结果, 不是字符串”BC”). 为什么会匹配到”B”, 因为”B”前面的位置紧接着的后面是”BC”, 而不是”AB”, 所以匹配成功.

于是零宽断言有什么用呢

比如如下字符串
ABXXABYAYCD
想要匹配的内容是”ABYAYCD”, 如果用
AB.*?CD
由于正则表达式是从左向右进行匹配的, 所以即使使用懒惰也会首先匹配到”ABXXABYAYCD”而不是所需的字符串, 而如果用
AB[^AB]*CD
则无法匹配到任何结果, 因为在要匹配的内容中”AB”和”CD”之间的”YAY”含有字符”A”, 而”[^AB]”会把”A”排除掉, 导致无法匹配, 合适的一个表达式是
AB((?!AB).)*CD
注意其中的”(?!AB).”匹配的内容, 而”((?!AB).)*”则是最外层括号内的内容重复任意多次. 这样就把字符串”AB”过滤掉不匹配而又不会过滤掉”A”或”B”

另一个例子

一个可能很常见的URL:

正则表达式零宽断言


想要匹配的内容是参数p的值, 也就是718
显然的想法是
p=(d+)
然后使用捕获组来获取这个”(d+)”的内容
如果利用零宽断言的话
(?<=p=)d+
匹配到的结果直接就是817, 不需要再用捕获组了

One thought on “正则表达式零宽断言”

Leave a Reply

Your email address will not be published. Required fields are marked *