正则表达式测试器(匹配位置 · 捕获组 · 替换预览)

输入正则和测试文本,实时列出每个匹配的位置、原文和各捕获组,未参与的组用 null 区分空串,并显示 $& / $1 / $<name> 的替换结果。全部在浏览器本地完成。

flags、空匹配与替换预览

难点不在写出来,在看不出来

写正则最费时间的不是写不出来,是看不出来:exec 返回了个数组,但你不知道它匹配在哪、匹配到什么、三个括号各自抓到了什么、有一个括号这次到底有没有参与。IDE 的高亮帮不上这个忙,因为它把捕获组和整段匹配混在一起显示。

这里把引擎的原始答案一条条列出来:每个匹配占一行,写起止位置、匹配到的原文(转义符、换行、制表符都按字面写出来,空格看得见)、以及每个捕获组。组那一格里 1: null 和 1: 是两回事:前者是那个括号这次根本没参与匹配,后者是它参与并匹配到了空字符串——这两件事在调试时完全不同。

flags 的口径

flags 那一格填什么就按什么构造正则,字母可选项是 d g i m s u y:

  • g 决定是否列全。开了就把所有匹配逐行列出(上限 200 行);不开只报告第一个匹配,提示语会写明"未开 g"。想数清楚有几个,就勾 g。
  • i 忽略大小写。m 让 ^ 和 $ 匹配每一行的边界,而不是整段文本的头尾——多行文本里 $ 匹配不上,九成是缺了 m。
  • s 让 . 也匹配换行符。默认情况下 . 不含 \n、\r、\u2028、\u2029,所以想"跨行取一段"必须开 s 或者用 [\s\S]。
  • u 打开 Unicode 模式:{2,10} 这类量词按码点而不是 UTF-16 码元计数,emoji 和生僻字不会被劈成两半,同时正则里的非法写法会直接报错而不是被宽容放过。
  • y 粘性匹配,只在 lastIndex 位置尝试;d 让结果带上 indices(每组起止下标)。这两条主要用来观察引擎行为,实际代码里用得少。
  • 填了不认识或重复的字母,会直接指出是哪个字母,而不是笼统报"语法错误"。

匹配数和空匹配

「匹配数」就是引擎实际返回的次数,包括空匹配。a* 在 bbb 上有 4 个匹配:位置 0、1、2、3 各一次空匹配,这不是 bug 而是正则定义的结果(零次重复也算匹配)。表格里这些行的原文写作「(空匹配)」,位置写作「3(空)」。

同理,^、$、\b 这些零宽断言本身不消耗字符,在带 g 的模式下会连续产生空匹配——如果你的匹配数多得离谱,先看看是不是写了个能空匹配的式子。工具会手动让 lastIndex 前进一格,所以不会死循环,但结果数量是引擎给的实话。

替换预览

模板那一格用的是 JavaScript 自己的替换语法(String.replace 的第二个参数),所以:

  • $& 是整个匹配,$` 是匹配之前的部分,$' 是之后的部分(这个记号很容易在文档里看漏)。
  • $1…$99 按编号取组,$<name> 按命名组取。编号大于正则里的组数时,这个记号原样留在结果里($3 还是 $3);组存在但这一次没参与匹配时,替换成空字符串。两者不一样,预览里能直接看出区别。
  • $$ 输出一个字面 $。所以你想把金额前加美元符号,得写 $$。
  • 没开 g 时只替换第一处。预览面板里那行结果就是这两条规则的产物,可以整段复制。
  • 空匹配在带 g 的替换里会在每个位置都插一次结果,a* 加模板 - 会把文本变成 -b--b--b- 这样。预览看到这种东西不要以为是工具坏了。

什么时候不能信这个结果

  • 卡住是有可能的。形如 (a+)+b、(x+x*)*y 这种嵌套量词碰上不匹配的长文本,会指数级回溯,浏览器整个标签页转圈,也没有超时保护。工具只在文本长度上设了 4000 字符的上限,这不足以挡住回溯爆炸;真怀疑有灾难性回溯,把文本换成两行短的试一次,别拿整段日志去试。
  • \p{...} 属性正则要求同时带 u flag(较新的引擎支持 v flag 也一样)。注意它不带 u 时不会报错:按历史规则 \p 被当成字面字母 p,于是 \p{Script=Han} 变成一个"匹配字面 p{Script=Han}“的式子,结果是 0 处匹配——看着像"中文匹配不上”,其实是 flag 漏了。这类静默降级是最容易误判的一处。
  • 数字组名是历史遗留:\1 在模式里是反向引用,(?<1>...) 这种写法在部分引擎里还能创建编号组名,这里不作特殊处理。
  • 位置是字符下标,UTF-16 码元为单位。带 emoji 或生僻字的文本里,它不等于肉眼看到的"第几个字符"。
  • 多行文本的行号不显示。表格里给的是下标,不是第几行第几列;对长日志建议先按行拆一遍。
  • 只测 JavaScript 正则。PCRE(PHP)、Python、Go RE2 的语法和特性都不同,尤其 RE2 不支持反向引用和 lookahead。在这儿测通的正则,换个语言要重新确认。

常见问题

Q:为什么表格只列 200 行? 200 之后基本是式子写宽了而不是还想看结果。计数那一格仍然显示真实的匹配数(截断时也一样),只是列表到此为止。真要全量,把文本切成几段分别测。

Q:捕获组为什么显示 null? 那是那个括号这一次没有参与匹配。比如 (a)|(b) 匹配 b 时,第 1 组就是 undefined,表格显示成 null(引擎数组里对应位置是 undefined)。而显示为空串的是参与匹配、抓到了 0 个字符,两者不一样。

Q:粘进来的文本会上传吗? 不会。正则由浏览器里的 new RegExp 构造,匹配和替换都由浏览器执行,你测的文本不会送到别处,也就不会离开这台设备。

Q:为什么我的中文没匹配上? 先看文本里有没有全角标点(: 和 : 不是同一个字符,正则里的 . 会匹配它们但 : 不会),再看是不是需要 u flag,最后确认正则没有被多写一层转义——从代码里粘 \\d 到这里应该是 \d。

微信
微信
支付宝
支付宝