正则表达式测试器(匹配位置 · 捕获组 · 替换预览)
输入正则和测试文本,实时列出每个匹配的位置、原文和各捕获组,未参与的组用 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{...}属性正则要求同时带uflag(较新的引擎支持vflag 也一样)。注意它不带 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。