使用方法
- 在「正则表达式」框里填模式,在下面的文本框里放待匹配的文本。页面打开时已预置一组能直接命中的示例:模式
(\d{4})-(\d{2})-(\d{2})配三行订单文本,结果区立刻显示命中位置与捕获组,不需要先点按钮。 - 六个标志用复选框逐项勾选,默认只勾
g。勾上g才返回全部匹配,不勾只返回第一个;i忽略大小写、m让^与$逐行生效、s让点号跨行、u按码点处理、y要求从上次结束的位置紧挨着匹配。 - 「匹配数」「捕获组」「高亮」三块同时更新:匹配数看总数,捕获组表逐行列出每次命中的整体与各组内容,高亮区在原文上标出命中片段,方便核对前后文。
- 模式写错时会指出编译失败的原因,而不是只弹一句「语法错误」;如果模式里出现
(a+)+这类嵌套量词,工具会先估算回溯代价,超过阈值就拒绝执行。 - 文本框里按 Ctrl + Enter(macOS 用 Cmd + Enter)立刻重跑;Enter 留给换行,因为多行文本是这里的常见输入。点「载入示例」还原样例,点「清空」从头开始。
这个正则测试工具能解决什么问题
一是在写进代码之前先把模式调通。正则的调试成本几乎全在定位阶段:到底是分支写反了、锚点位置不对,还是量词贪婪多吃了几个字符。把模式放进来,配合高亮区看一眼命中的边界,通常比在编辑器里反复改字符串更快。
二是把捕获组的编号和命名对上。分组一旦嵌套,\1、\2 到底指谁极易记错。这个正则测试工具会把每次命中的整体、每个编号组以及命名组列成一张表,编号顺序就是左括号出现的顺序。
三是验证标志组合的实际效果。同一个模式加上 m 或 s 之后行为会整体变化,光看文档不如直接勾一下看结果;下面的对照表给出了十几组组合在具体输入上的命中次数。
四是提前发现会让页面卡死的写法。有些模式在短文本上瞬间返回,换成长文本却要跑上亿步;正则表达式测试工具会在执行前识别嵌套量词并给出步数估算。
本页不做这些事:不做文本替换、不生成供代码使用的转义字符串、不提供语法条目查询。它只回答一个问题——这个模式在这段文本上到底命中了什么。
匹配原理:ECMAScript 的六个标志、lastIndex 与捕获组
模式通过 new RegExp(pattern, flags) 编译,引擎是 V8 的 Irregexp,属于回溯式实现:从每个起点依次尝试,走到死路就退回上一个选择点换一种走法。理解这一点,后面所有「为什么这么慢」的问题都有了解释。
六个标志的语义要逐条记清。g(global)让匹配继续往下找,不勾只返回第一个;i(ignoreCase)按 Unicode 大小写折叠比较;m(multiline)让 ^ 和 $ 在每行首尾都成立,实测 /^a/m 对「x 换行 a」命中 1 次;s(dotAll)让点号也能匹配换行,/a.b/s 对「a 换行 b」命中 1 次,不勾 s 则 0 次;u(unicode)按码点而不是码元处理,/^.$/u 对单个表情符号命中 1 次,不勾 u 是 0 次,因为那个表情在 UTF-16 里占两个码元;y(sticky)要求从 lastIndex 指的位置紧挨着开始,/a/y 对 banana 命中 0 次,因为首字符是 b。另有 d 标志,它给每次命中附上 indices 属性,例如捕获组 1 的位置可以取到半开区间 [5, 9]。
g 之下的 lastIndex 是这一页最要紧的陷阱。带 g 的 RegExp 对象是有状态的:每次 exec 或 test 都把 lastIndex 推到本次命中的末尾,下一次调用从那里继续。实测把同一个 /a/g 连续 exec 四次,对 aba 依次得到「命中@0(lastIndex=1)→ 命中@2(lastIndex=3)→ null(lastIndex 归零)→ 命中@0」;换成 test 就是 true → true → false → true。很多「匹配数莫名其妙少一个」的 bug 就是这么来的:复用了同一个正则对象,第二次调用从上次的末尾开始数。正则表达式测试工具每次执行都重新构造 RegExp,并且优先用 String.prototype.matchAll——它要求必须带 g,返回的是迭代器,内部游标是副本,不会污染原对象,也不需要你手写 lastIndex = 0。取消 g 之后对象 lastIndex 恒为 0,那种写法才是可复用的。
捕获组的编号规则是「左括号出现的顺序」。(\d{4})-(\d{2})-(\d{2}) 里第一个左括号是组 1,第二个是组 2,第三个是组 3,与它们包裹的内容长短无关。命名组 (?<y>\d{4}) 除了进 m.groups,同样占用一个编号,会挤掉后面所有组的编号,所以两种写法混用时编号常常和你想的不一样。某一次命中里如果某个组没参与,取到的值是 undefined,本工具会写「(未参与)」而不是留空,免得你以为它匹配到了空字符串。
零宽匹配要单独说一句。像 a* 或先行断言这类可以匹配空串的模式,在 g 之下会在每个位置都「命中」一次长度为 0 的结果。工具用一个竖线标出这种命中,但它不计入「高亮字符数」,因为确实没有字符被覆盖。另外,matchAll 自身会正确处理零宽匹配的前进问题,而手写 while (m = re.exec(s)) 的循环若不手动推进 lastIndex,遇到零宽命中会死循环。
完整示例(数值仅作演示)
示例条件与页面打开时完全一致:模式 (\d{4})-(\d{2})-(\d{2}),只勾 g,文本是那三行订单。实测匹配数 2:
- 第 1 次命中从第 5 个字符处开始,整体是
2026-10-01(位置 5 到 15);组 1 =2026、组 2 =10、组 3 =01; - 第 2 次命中从第 25 个字符处开始,整体是
2025-12-25(位置 25 到 35);组 1 =2025、组 2 =12、组 3 =25; - 第三行的
2026-1-5不命中,因为月和日各只有 1 位,凑不满\d{2}——这就是「模式写宽会误判、写严会漏判」的典型例子。
文本全长 61 个字符,两次命中各 10 个字符,高亮字符合计 20 个、占全文 32.8%。模式里有三个左括号,所以捕获组数是 3;两次命中里三个组都完整参与,于是捕获组表 2 行 × 3 列全部有值。改成带命名组的写法结果一样,只是多了一条按名字取值的路径。
标志组合与匹配行为对照表
下表每一行都是一次真实执行的结果,「匹配数」一栏可以直接和页面上的数字对照。要分清「模式写错」和「标志没勾对」:前者会报错,后者只会安静地少匹配几次。
| 标志 | 模式 | 输入文本 | 匹配数 | 说明 |
|---|---|---|---|---|
| 无 | a | banana | 1 | 不加 g 只返回第一个匹配 |
| g | a | banana | 3 | 三个 a 全部返回,位置 1、3、5 |
| i | A | Bananas | 1 | 大小写折叠后首字母命中 |
| m | ^a | x 换行 a | 1 | 不加 m 时 ^ 只认全文开头 |
| s | a.b | a 换行 b | 1 | 点号被允许跨过换行符 |
| (对照) | a.b | a 换行 b | 0 | 同一模式不勾 s,点号不跨行 |
| u | ^.$ | 一个表情符号 | 1 | 按码点算,整个表情是一个字符 |
| (对照) | ^.$ | 一个表情符号 | 0 | 不勾 u 时按码元算,两个码元撑破一个点号 |
| y | a | banana | 0 | 粘滞要求从位置 0 紧挨着开始,首字符是 b |
| gi | A | bAnana | 3 | 全局加忽略大小写,大小写 a 一起算 |
| gm | ^a | x 换行 a 换行 a | 2 | 两行的行首都以 a 开头 |
| gu | \p{Script=Han}+ | abc汉字def | 1 | Unicode 属性转义,必须带 u 才能用 |
| g | (?<y>\d{4})-(?<m>\d{2}) | 2026-10 / 2025-12 | 2 | 命名组进 groups,同时照样占编号 |
| g | \d(?=元) | 1元2元 | 2 | 先行断言,只断言后面是「元」但不吃掉它 |
| g | (?<=\d)元 | 1元2元 | 2 | 后行断言,要求前面是数字 |
| g | (\d{4})-(\d{2})-(\d{2}) | 本页示例的三行订单文本 | 2 | 与页面上方的结果一致,第三个日期位数不足 |
边界与性能说明
- 灾难性回溯可以给出具体步数。模式
(a+)+$匹配n个a再跟一个感叹号时,引擎要穷举这n个字符的所有拆分方式,步数是2n量级。实测:n=20走 1,048,576 步、约 12 毫秒;n=25走 33,554,432 步、约 366 毫秒;n=28走 268,435,456 步、约 2.96 秒。每多一个字符就翻一倍,n=35约 340 亿步,标签页必死。 - 执行没有超时机制。正则匹配是同步的,进去了就不能中断,页面按钮一个都点不动,只能关掉标签页。所以正则表达式测试工具在启动前做了拦截:模式里出现嵌套量词(如
(a+)+、(\w+)*)且文本超过 24 个字符时直接拒绝执行;24 个字符以内照常跑,因为那最多 1,048,576 步。 - 把嵌套量词改成单层量词就能救回来。
(a+)+与a+在由 a 组成的文本上结果一样,但前者指数级、后者线性;(\w+)*通常可以直接写成\w*。 - 文本上限 10 万字符。超过之后文本框不再接收新内容,已在里面的不会丢失。匹配结果最多渲染 2000 次命中,再多就截断并提示——不是匹配不到,而是页面画不动。
- 模式里尽量别用点号通配整篇文档。
.*会先吞掉全部内容再逐步回退,行为和回溯爆炸类似,只是没那么极端;能写具体字符类就别写点号。 - 正则不适合解析 HTML 或 XML。标签可以任意深度嵌套,而正则只能识别正则语言。提取某一层的属性值这类扁平片段可以用,整篇文档请交给解析器。
- 各语言的正则并不完全通用。后行断言在 JavaScript、.NET、Python 里都支持,但部分引擎只接受定长后缀;命名组写法各不相同;必须带
u才能用的\p{Script=Han},换到别的引擎上未必认。
常见问题
为什么我勾了 g 以后,代码里的匹配数少了一个?
几乎可以肯定是复用了同一个带 g 的正则对象。它对 test 和 exec 都保留 lastIndex,第二次调用接着上一次命中之后继续找。实测同一个 /a/g 连续 test 四次对 aba 返回 true → true → false → true,中间那个 false 就是这么冒出来的。修法:每次重新建对象,或改用 matchAll。
明明有内容却匹配 0 次,从哪里查起?
按这个顺序排查:先看标志——该跨行的点号没勾 s,或者只需要一个匹配却开了 m;再看锚点——^ 和 $ 在不带 m 时只认全文首尾;最后看转义——匹配真正的点号要写 \.,而 . 是通配符。
y 和 g 到底差在哪?
g 允许引擎在后面的任意位置继续找下一个匹配;y 只允许从 lastIndex 指的位置紧挨着开始,找不到就立即失败,不往后挪。实测 /a/y 对 banana 匹配 0 次,因为位置 0 是 b;同样文本换成 /a/g 是 3 次。
捕获组里显示「(未参与)」是什么意思?
表示这一次命中走了另一条分支,那个组根本没被匹配到,取到的值是 undefined。比如 (a)|(b) 命中 a 时,组 2 就是未参与。把它和「匹配到空字符串」区分开很重要:后者的值是空串,长度 0 但确实匹配上了。
高亮区里出现的那个竖线是什么?
是零宽命中。像 a*、\b、先行断言这类可以匹配空串的模式,在 g 之下会在每个位置都产生一次长度为 0 的命中。它不占字符,所以不计入「高亮字符数」,但匹配数会把它算进去——这就是「匹配数」有时远大于肉眼所见片段数的原因。
提示嵌套量词不让执行,该怎么改?
把内层的量词去掉就行。(a+)+ 改成 a+,(\w+)* 改成 \w*,要保留分组语义就写成 (\w+) 再自己控制外层次数。这种改写的正确性来自语言本身的等价性,不是近似替换。
为什么匹配的位置和我想的边界不一样?
多半是贪婪与懒惰的区别:量词默认贪婪,能多吃就多吃。<.+> 会把整行从第一个尖括号一直吃到最后一个,改成 <.+?> 才会只取第一对。另一个常见原因是零宽断言不吃字符,命中的区间看起来会比预期短一截。
我输入的内容会被上传或保存吗?
不会。这个正则测试工具不会发出任何网络请求,编译、匹配、高亮渲染全部在页面里完成,也不会写入浏览器存储。想自己验证,打开开发者工具的 Network 面板,连续点几次「测试匹配」即可。
相关工具
- 文本差异检测:逐行比对两段文本的增删改,核对改动前后的字段。
- JSON格式化工具:把接口返回的一整行 JSON 展开成缩进结构,排查字段比正则可靠。
- Cron表达式解析:把
*/5 * * * *翻成下次执行时间,它也有自己的语法规则。 - HTTP状态码代码速查表:按状态码查含义,写判断响应码的模式前先确认边界。
- Slugify字符串工具:把中文标题转成拼音或短横线形式的网址片段,内部同样是一串替换规则。
- HTML校验:整篇文档的结构问题交给它,不要用正则去数标签配没配对。
- Git速查表:常用子命令与参数速查,写
git log --grep时能对上参数写法。 - 开发者工具:查看全部开发者工具。