正则不匹配时,先查这三件事

写正则只要十分钟,找出为什么不匹配常常要一小时。这篇文章把排查顺序固定下来。

写完一条正则,测试数据里有的匹配、有的不匹配,这是最常见的场景。真正耗时的是「为什么不对」。下面三个坑覆盖了绝大多数情况,而且都有固定的排查顺序。

先分清两种失败

没匹配上和匹配上了但取错内容是两类问题。前者通常是字符集或标志写错,后者多半是量词的贪婪与分组括号的位置问题。先确定属于哪一类,能省掉一半时间——在调试面板里先看「有没有匹配」,再看「第 1 组取到了什么」。

坑一:贪婪量词吃掉了后面的内容

.* 默认是贪婪的,它会一路吃到行尾,再回溯来找后面的模式。所以 title="(.*)" 匹配 title="a" id="b" 时,捕获组拿到的是 a" id="b,不是 a。

改法有两种:把量词改成懒惰的 .*?,或者用更精确的字符类 [^"]*。后者通常更快也更稳——懒惰量词仍然会回溯,字符类不会。

坑二:转义层级多了一层或少了一层

正则写在字符串里时,反斜杠要经过两层解析。JavaScript 里写 \d 才是正则的 \d;从别处复制来的 \\d 会变成匹配字面量反斜杠加 d。同理,\s、\.、\/ 都容易多一层。

快速判断:在调试工具里打印出「最终的 pattern 字符串」,看它和你以为的是否一致。这一条能解决大量「明明写了却没匹配」的困惑。

坑三:m 与 g 两个标志

没加 m 时,^ 和 $ 只匹配整个字符串的开头和结尾,多行文本里想按行匹配就会失败。加上 m 之后,它们才会认每一行的首尾。

加了 g 时,正则对象会记住上一次匹配的位置(lastIndex)。在循环里复用同一个带 g 的正则,第二次调用会从上次结束的位置继续找,看起来就像「漏匹配」。每轮新建一个实例,或者把 lastIndex 归零。

四步排查流程

  1. 先缩小范围:把测试串截到最短能复现的长度,噪声越少越容易看清
  2. 逐段试:把正则按|拆开,或把可选部分删掉,找出哪一段开始失败
  3. 看分组:确认每个捕获组实际取到什么,而不是你以为的
  4. 换输入验证:换一条应该匹配、一条应该不匹配的数据,确认没有「过度匹配」

第四步最容易被跳过。一条正则能匹配上目标,不代表它不会把不该匹配的东西也吃进去——「过度匹配」往往比「不匹配」造成更严重的后果,比如把用户输入的整段 HTML 当成纯文本替换掉。

顺带一句性能

像 (a+)+$ 这样的嵌套量词遇到长串不匹配的输入,会产生灾难性回溯,把浏览器卡死几秒钟。判断方法很简单:输入长度翻倍,耗时是否翻了四倍以上。如果是,就别在用户输入上直接用这类正则。

一句话总结

先分清是「没匹配」还是「取错」,再依次查贪婪、转义、标志。 按这个顺序走,绝大多数正则问题不用猜。

调试面板上该看哪三个数字

打开正则测试工具,第一眼不要看高亮好不好看,先看这三个信息:

  • 匹配总数。0 就是「完全没匹配」,属于字符集或标志问题;大于 0 但比你预期少,通常是 g 标志忘了开,或者某一段把后面的内容吃掉了
  • 每个匹配的起止位置。位置比内容更能说明问题——如果第一个匹配一直延伸到文本末尾,那就是贪婪量词在作怪
  • 第 1 组捕获到什么。单独看分组内容,能立刻区分「没匹配」和「匹配了但取错」这两类问题

顺序很重要:先确认总数,再看位置,最后看分组。反过来做,你会被高亮的颜色带着走,越看越乱。

中文和 Unicode 的三个盲点

w 等价于 [A-Za-z0-9_],不包含任何汉字。所以 w+ 去匹配「张三」会得到零个结果。想匹配中文,用 [一-龥],或更宽的范围 [一-鿿]。

. 在非 s 模式下不匹配换行符。一段带换行的多行文本,用 .* 只能吃到行尾。要么改 [sS]*,要么开 s 标志。

 词边界对中文基本无效——中文词之间没有空格, 找不到边界。做中文分词应该用专门的分词库,而不是正则。

还有一点:中文标点是全角字符,, 和 , 是两个不同的码位。校验表单时写了半角逗号,用户输入全角逗号就会失败。

用一张用例表把正则固定下来

「测试的时候能过」不等于「正则是对的」。可靠的做法是给每条正配备三到五条用例,形成一张小表:

  • 应该匹配的:正常输入、边界输入(最短、最长、刚好卡在边界上)
  • 应该不匹配的:空字符串、只有空白、多了前缀或后缀的脏数据
  • 容易误匹配的:形似但不同类的输入,例如校验手机号时用「12 位数字」和「以 1 开头的座机号」去撞

这张表的价值在改动之后——半年后有人来改这条正则,跑一遍用例表就知道有没有破坏原有行为。没有用例表,任何修改都是盲改。

这些场景不要用正则

正则不是万能工具,有些地方用了必然出问题:

  • 解析嵌套的 HTML 或 XML。标签可以任意嵌套,正则没有递归能力。<div>.*?</div> 永远只能吃到第一个闭合标签,外层结构全错。用 DOM 解析器
  • 解析 JSON。字符串里可以出现任意转义和花括号,正则会误判。用 JSON.parse
  • 校验邮箱的完整合法性。RFC 5322 允许的写法极其复杂,用正则追求 100% 准确既难写又难维护。实用做法是限制长度、要求有一个 @ 和至少一个点,然后发验证邮件
  • 处理超长输入。带嵌套量词的正则遇上几万字符的不匹配输入会指数级回溯,把浏览器卡死。处理大文本前先做长度上限

判断标准很简单:目标结构是否允许任意嵌套或递归? 是,就别用正则。

相关工具