正则表达式:先行与后行断言

断言(lookaround)用于检查匹配位置前后是否满足某个条件,但断言中的文本不会包含在最终匹配结果里。它适合“提取某段内容、但要求它有特定上下文”的场景。

兼容性

正向与负向先行断言支持较广;RE2 明确不支持 lookaround。Python re 的后行断言内部必须匹配固定长度,其他引擎对可变长度和分支长度有各自限制。JavaScript 支持还取决于实际运行时版本。遇到兼容性问题时,可改用捕获组并从结果中读取目标部分。

1. 正向先行断言

功能说明

正向先行断言 (?=...) 要求当前位置后面的文本能匹配括号中的模式,但不把这段后续文本纳入结果。它常用于“匹配某数值,但要求后面跟着某单位”。

语法格式

目标(?=后续条件)

简单示例

\d+(?=元)

测试文本:价格 120元,数量 3件

匹配结果说明

结果是 120,而不是 120元。元 只作为必须满足的后续条件,不会成为匹配结果的一部分;3 后面是 件,因此不匹配。

2. 负向先行断言

功能说明

负向先行断言 (?!...) 要求当前位置后面的文本不能匹配指定模式。它适合排除某些后缀、关键字或格式,但要保证断言放在正确的位置,避免只排除了部分文本。

语法格式

目标(?!不允许的后续条件)

简单示例

\b\w+\.(?!exe\b)\w+\b

测试文本:readme.txt setup.exe image.png

匹配结果说明

结果是 readme.txt 和 image.png。在点号之后,断言要求扩展名不能是完整的 exe;因此 setup.exe 被排除。这个规则只检查扩展名结构,不代表文件一定安全。

3. 正向后行断言

功能说明

正向后行断言 (?<=...) 要求当前位置前面的文本满足某个模式,但不把前面的文本纳入结果。它常用于从带前缀的字段中只提取主体内容。

语法格式

(?<=前置条件)目标

简单示例

(?<=\$)\d+

测试文本:$120 €80 60

匹配结果说明

结果是 120,因为它紧接在美元符号 $ 之后。80 和 60 不满足前置条件。若运行环境不支持后行断言,可先匹配 \$(\d+),再读取第一个捕获组。

4. 负向后行断言

功能说明

负向后行断言 (?<!...) 要求当前位置前面的文本不能匹配指定模式。它适合匹配“不带某前缀”的内容;与正向后行断言一样,需注意引擎对后行长度与语法的限制。

语法格式

(?<!不允许的前置条件)目标

简单示例

(?<!\$)\b\d+\b

测试文本:$120 80 €60

匹配结果说明

结果是 80 与 60,而 $120 中的 120 因为前一个字符是 $ 而不匹配。这个模式只检查紧邻位置;若业务规则是“整段文本中不能含货币符号”,需要写更完整的规则或先做结构化解析。

5. 断言与捕获组的选择

功能说明

断言适合“检查但不返回”的上下文;捕获组适合“匹配并取出”的上下文。两种写法有时都能完成任务,选择时优先考虑兼容性和后续代码是否容易读取结果。

语法格式

断言:(?<=前缀)目标
捕获:前缀(目标)

简单示例

(?<=ID:)\d+
ID:(\d+)

测试文本:ID:42

匹配结果说明

第一个模式的整体匹配就是 42;第二个模式的整体匹配是 ID:42,但第一个捕获组为 42。第二种写法在不支持后行断言的环境中更通用。

练习与自测

  1. 用先行断言从 120ms 80s 中只提取后接 ms 的数字;再改用捕获组实现。
  2. 在 Python 中尝试编译 (?<=a+)b,记录异常;解释“固定长度后行”的含义。
  3. 将“排除 .exe 后缀”的规则写出 readme.txt、archive.exe、archive.exe.bak 三个测试,先定义你要排除哪一种。
  4. 为什么断言不消耗文本,却仍可能参与回溯?
  5. 若目标引擎是 RE2,应如何把依赖 lookaround 的提取拆成普通匹配加程序判断?

上一节:分组、分支与反向引用|下一节:标志与替换。