← 返回文章列表

正则表达式实用配方与四个常见陷阱

正则避坑

先接受一个事实:正则不是万能的

正则擅长结构清晰、规则固定的匹配:日志行、配置片段、格式初筛。它不擅长嵌套结构——HTML、JSON、代码语法应使用对应的解析器,用正则解析嵌套内容迟早会出错。

高频配方

需求正则说明
邮箱(宽松)[\w.+-]+@[\w-]+\.[\w.-]+只做格式初筛,最终以发信验证为准
中国大陆手机号1[3-9]\d{9}号段会调整,不要硬编码全部号段
IPv4(四段)(?:\d{1,3}\.){3}\d{1,3}不校验 0-255 范围,需另做数值判断
ISO 日期\d{4}-\d{2}-\d{2}不校验合法性,2026-19-99 也会通过
去除首尾空白^\s+|\s+$多数语言直接用 trim() 更合适
连续重复行去重^(.*)(?:\r?\n\1)+$需开启多行模式

四个必须掌握的机制

  1. 贪婪与懒惰:.* 会尽可能多匹配,加 ? 变成 .*? 改为尽可能少,这是最常见的 Bug 来源;
  2. 分组与捕获:() 捕获、(?:) 不捕获;反向引用 \1 可匹配与前面相同的内容;
  3. 环视:(?=...) 正向先行、(?!...) 负向先行,用于「后面必须是 / 不能是」的判断;
  4. 锚点与模式位:^ $ 配合 m(多行)、s(点匹配换行)使用,语义完全不同。

灾难性回溯(ReDoS)

像 (a+)+$ 这类嵌套量词在遇到不匹配的长字符串时,匹配时间会指数级增长,导致 CPU 打满。规避方式:避免嵌套量词、在支持的语言里使用独占量词或原子组、对用户输入长度设上限、不要让用户可控的正则进入服务端匹配。

实用建议

  • 先准备「应匹配」与「不应匹配」两组样例,再写正则;
  • 复杂校验拆成「正则初筛 + 代码精确校验」,别指望一条正则做完;
  • 使用命名分组让后续取值更易读;
  • 把正则当代码维护:加注释、存为常量、参与评审。

常见问题

为什么 .* 匹配了整行?因为它贪婪,改用 .*? 或限定字符类(如 [^"]*)。为什么 \d 匹配到了全角数字?取决于引擎是否开启 Unicode 模式,需要时显式写 [0-9]。正则能校验密码强度吗?可做长度与字符类检查,但强度本质是熵,专门的强度评估更合适。

调试建议

用正则测试工具(或编辑器的正则搜索)分三段验证:先验证字符类,再加量词,最后加锚点。把量词与分组拆开测试,能快速定位是哪一部分让匹配超出预期。同时准备「不应匹配」的样例——只测试能匹配的情况,会漏掉最严重的过度匹配问题。

相关阅读:强密码的真相:长度比复杂度更重要

让正则长期可维护

正则的难点不在写出来,而在半年后还能看懂和修改。

  1. 命名与注释:把常用正则定义为具名常量,并在上方注释说明它接受什么、拒绝什么。把一段表达式直接内联在业务代码里,是最容易失控的写法。
  2. 拆分组合:把复杂表达式拆成若干可单独测试的小片段,再组合使用。既能复用,也能在出错时快速定位是哪一段不匹配。
  3. 配套测试用例:为每个正则准备一组“应匹配”与“不应匹配”的样本,尤其是临近边界的输入,例如只有一位数字、超长字符串与包含换行的文本。
  4. 优先用更简单的工具:判断是否包含子串、是否以某前缀开头这类需求,用普通字符串方法即可,更快也更易读,不必动用正则。
  5. 注意语言差异:不同语言的实现对手写语法、锚点与转义的处理并不一致,跨语言移植时应重新验证,而不是直接复制。

把这些习惯固定下来,正则会从“只有原作者敢改”的代码,变成团队里可以放心维护的普通逻辑。