正则表达式(Regular Expression)是用一套特殊语法描述字符串模式的工具,几乎出现在每一门编程语言、每一款编辑器和每一个命令行里。掌握它,你就能用一行规则完成别人上百行代码的文本提取、校验与替换。
一、正则能解决什么:三个典型场景
| 场景 | 例子 | 不用正则的代价 |
|---|---|---|
| 数据校验 | 邮箱、手机号、身份证格式 | 手写几十行逐字符判断 |
| 文本提取 | 从日志里抓 IP、URL、订单号 | 按固定位置截取,脆弱易错 |
| 批量替换 | 把 YYYY-MM-DD 重排成 DD/MM/YYYY | 循环 + 字符串拼接,可读性差 |
二、核心语法速查
2.1 字符类与锚点
\d 数字、\w 单词字符、\s 空白;. 匹配除换行外任意字符;^ 行首、$ 行尾、\b 单词边界。字符组用 [a-z0-9] 表示区间,[^0-9] 取反。
2.2 量词与贪婪
* 0 次或多次、+ 1 次或多次、? 0 或 1 次、{m,n} 次数区间。默认贪婪(尽可能多匹配),加 ? 变惰性(尽可能少)。
2.3 分组、捕获与断言
(...) 捕获分组,(?:...) 非捕获;(?=x) 正向先行、(?!x) 负向先行、(?<=x) 正向后行、(?<!x) 负向后行。断言只定位置、不消耗字符,是精准提取的利器。
import re
text = "订单号 ORD-2026-1001, 联系电话 13800138000"
m = re.search(r"ORD-\d{4}-\d{4}", text)
print(m.group()) # ORD-2026-1001
# 命名捕获,更易维护
m2 = re.search(r"(?P<area>\d{3})-(?P<num>\d{8})", "010-13800138")
print(m2.group("area")) # 010
三、实战一:表单数据校验
前端校验能第一时间挡住明显错误,减轻后端压力。下面是一组可直接复用的规则,注意手机号用 ^1[3-9]\d{9}$ 锚定首尾,避免子串误判。
const email = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
const phone = /^1[3-9]\d{9}$/;
const url = /^https?:\/\/[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=]*$/;
console.log(email.test("dev@fsdata.site")); // true
console.log(phone.test("13800138000")); // true
四、实战二:从日志里提取关键信息
服务器排障时,正则配合 grep、awk 能秒级定位异常。这和我们在《Web 服务器日志分析实战》里用命令行分析访问日志的思路一脉相承:用模式而非固定位置去抓数据。
# 统计访问量最高的前 5 个 404 客户端 IP
grep " 404 " access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -5
# 直接抓出日志里的所有邮箱
grep -oE "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" access.log
# 提取 Nginx 访问行中的状态码
grep -oE '" [0-9]{3} ' access.log | sort | uniq -c
五、实战三:在编辑器与 CI 里用正则
VS Code 的查找替换支持正则,$1、$2 引用捕获组,可批量重构变量名;GitHub Actions 则用 paths 的 glob 决定哪些改动才触发流水线。两者底层都是”模式匹配”思想。
# VS Code 查找替换:把 2026-10-22 重排为 22/10/2026
查找: (\d{4})-(\d{2})-(\d{2})
替换为: $3/$2/$1
# GitHub Actions:仅 src 下改动才跑测试
on:
push:
paths:
- 'src/**'
- '!src/**/*.test.ts'
六、贪婪 vs 惰性:一个经典坑
用 .* 跨标签匹配时,贪婪会一次吞掉整段。加上 ? 变惰性才能逐个命中。这也是《ReDoS 正则回溯炸弹》要强调的:不当的量词组合会让匹配复杂度爆炸。
import re
s = "<div>甲</div><div>乙</div>"
print(re.findall(r"<div>.*</div>", s))
# ['<div>甲</div><div>乙</div>'] 贪婪:整段
print(re.findall(r"<div>.*?</div>", s))
# ['<div>甲</div>', '<div>乙</div>'] 惰性:逐个
七、多语言方言对照
| 特性 | JavaScript | Python | Java | Go |
|---|---|---|---|---|
| 命名捕获 | (?<name>…) | (?P<name>…) | (?<name>…) | (?P<name>…) |
| 点匹配换行 | /s | re.S | Pattern.DOTALL | (?s) |
| 忽略大小写 | /i | re.I | CASE_INSENSITIVE | (?i) |
| 非捕获组 | (?:…) | (?:…) | (?:…) | (?:…) |
八、性能与安全:别踩回溯炸弹
嵌套量词如 (a+)+ 遇到不匹配的输入会指数级回溯,形成 ReDoS。防御手段:用具体字符类收窄(如 [^"]* 代替 .*)、避免重叠量词、在关键路径加超时。日志采集、fail2ban 的 filter 正则都需警惕这一点。
| 写法 | 风险 | 建议 |
|---|---|---|
| \d+ 匹配长数字 | 可能超长越界 | 加边界 {m,n} |
| (a+)+ 嵌套量词 | 回溯炸弹 ReDoS | 用原子组或固化 |
| .* 跨行 | 意外吞掉多行 | 用 [^”]* 或惰性 |
九、落地清单
| 动作 | 要点 |
|---|---|
| 先写单测 | 用匹配/不匹配两组样例验证 |
| 锚定边界 | 校验务必加 ^ 和 $ |
| 收窄字符 | 能用 [a-z] 就别用 . |
| 慎用嵌套 | 避免 (x+)+ 类结构 |
| 利用工具 | regex101 / VS Code 实时高亮 |
十、实战四:用 re.sub 做结构化转换
正则不只是”查找”,配合替换还能完成结构化转换。Python 的 re.sub 第二个参数可以传一个函数,对每一次匹配做定制处理;JavaScript 的 String.replace 同样支持替换函数,能轻松实现”把匹配到的数字统一换算”这类逻辑,而不必先切分再拼接。
import re
# 把文本里的金额从"元"换算成"万元"
text = "项目A预算 500000 元,项目B预算 1200000 元"
out = re.sub(r"(\d+) 元",
lambda m: "%.2f 万元" % (int(m.group(1)) / 10000),
text)
print(out)
# 项目A预算 50.00 万元,项目B预算 120.00 万元
十一、断言的组合实战:密码强度校验
零宽断言是正则里最容易被忽略、却最实用的能力。例如”密码必须至少 8 位,且同时包含小写、大写字母与数字”,可以把三条先行断言叠加在开头表达,而不必写三段独立判断,可读性和准确性都更好。
import re
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"
print(bool(re.match(pattern, "Passw0rd"))) # True
print(bool(re.match(pattern, "password"))) # False:缺大写与数字
print(bool(re.match(pattern, "Pas0"))) # False:长度不足
十二、什么时候不该用正则
正则不是万能钥匙。解析 HTML、JSON、XML 这类具备层级结构的数据时,用正则既脆弱又难维护——标签嵌套、属性转义、跨行内容都会让精心写好的规则瞬间失效,还容易引入安全漏洞。正确做法是先调用对应的解析器把结构读出来,再对提取出的纯文本片段使用正则。正则负责”模式”,解析器负责”结构”,二者分工才清晰。
| 任务 | 是否适合正则 | 推荐做法 |
|---|---|---|
| 校验邮箱、手机号格式 | 适合 | 单行正则锚定首尾 |
| 从日志提取 IP / URL | 适合 | grep / awk 配合正则 |
| 解析 HTML 页面结构 | 不适合 | 用 HTML 解析器(如 BeautifulSoup) |
| 读取 JSON 字段 | 不适合 | 用 json 解析库 |
| 拆分层级配置 | 不适合 | 用 YAML / TOML 解析器 |
十三、调试与常见误区
写正则最常见的两类错误是”忘记锚定边界”和”滥用点号”。校验场景漏写 ^ 和 $,会让子串误判为通过;而用 .* 跨行匹配,又容易吞掉本不该匹配的内容。调试时优先借助 regex101、RegExr 这类在线工具实时高亮匹配范围,再用真实样例(既要包含应匹配的,也要包含应拒绝的)做回归。
| 误区 | 后果 | 修正 |
|---|---|---|
| 校验漏写 ^ $ | 子串也能通过 | 锚定首尾边界 |
| 用 .* 代替具体字符 | 跨行误吞、性能差 | 改用 [^”]* 或惰性 |
| 嵌套量词 (a+)+ | 回溯炸弹 ReDoS | 原子组 / 固化分组 |
| 用正则解析 HTML | 规则脆弱易崩 | 改用专用解析器 |
十四、速查与下一步
把本文的语法速查、方言对照与避坑清单存成自己的笔记,遇到文本处理需求时先问一句”能不能用一行正则解决”。想继续深入,可以进一步研究正则引擎在 NFA 与 DFA 上的差异、占有量词与原子组的性能意义,以及各语言正则库在 Unicode 处理和回溯策略上的细微差别。把这些吃透,你就能在日志分析、数据清洗和接口校验中真正游刃有余。




