正则表达式完全指南:从基础语法到多语言实战 | 2026 最新实践
你有没有遇到过这些场景:
- Nginx 日志几十万行,老板要你统计 Top 10 IP 和 404 请求
- 配置文件里 200 个
http://链接要全部改成https:// - 用户注册要验证邮箱、手机号格式,写了一堆 if 判断还是漏了边缘情况
- Vim 里想选中所有引号内的字符串,却不知道怎么搜索
- Python 脚本要从 HTML 里提取所有
<a>标签的链接
正则表达式就是解决这些问题的通用工具——用一个简洁的表达式描述文本模式,然后匹配、提取、替换。
它是 Shell 脚本(grep/sed/awk)、Python 编程、Vim 编辑器、日志分析的共同基础。学会了正则,你在以上所有场景中都能事半功倍。
本文从零开始,系统讲解正则表达式的完整知识体系:
- 基础语法:元字符、量词、锚点、字符类
- 分组与引用:捕获组、非捕获组、命名分组、反向引用
- 进阶技巧:零宽断言、贪婪与非贪婪、模式修饰符
- 多环境实战:grep/sed/awk/Vim/Python/JavaScript 六大场景
- 50+ 常用正则模式库:邮箱、手机号、URL、IP、日期等
- 性能优化与调试:避免灾难性回溯、在线测试工具
一、正则表达式基础概念
1.1 什么是正则表达式
正则表达式(Regular Expression,简称 Regex 或 Regexp)是一种描述字符串模式的微型语言。它不是一个程序,而是一套语法规则,被几乎所有编程语言和命令行工具支持。
简单来说:
普通文本搜索:搜索 "error" → 只匹配 error 这个词正则表达式搜索:搜索 "err[a-z]+" → 匹配 error, errors, errcode, errlevel...1.2 三种正则引擎
| 类型 | 全称 | 特点 | 使用场景 |
|---|---|---|---|
| BRE | Basic Regular Expression | 元字符需转义,功能最少 | grep、sed 默认模式 |
| ERE | Extended Regular Expression | 元字符直接使用,功能够用 | grep -E、sed -E、awk |
| PCRE | Perl Compatible Regular Expression | 功能最全,支持断言/命名组 | Python re、Perl、grep -P |
实用建议:
- 命令行统一用
grep -E(ERE 足够且兼容性好) - 编程语言直接用语言内置 PCRE 引擎
- 记住差异:BRE 中
+ ? | () {}需要写\+ \? \| \(\) \{\}
1.3 第一个正则表达式
# 匹配包含 "error" 或 "Error" 的行grep -E '[Ee]rror' /var/log/syslog
# 匹配以数字开头的行grep -E '^[0-9]' file.txt
# 把所有 http:// 替换成 https://sed -E 's/http:\/\//https:\/\//g' config.txt正则表达式的核心思路:用特殊符号(元字符)描述文本模式,用普通字符匹配字面内容。
二、元字符与字符类
2.1 核心元字符速查表
| 元字符 | 含义 | 示例 | 匹配 | 不匹配 |
|---|---|---|---|---|
. | 任意单个字符(除换行) | a.c | abc, a1c, a c | ac, aBC |
\d | 数字 [0-9](PCRE) | \d{3} | 123, 456 | abc, 12 |
\D | 非数字 | \D+ | abc, !@# | 123 |
\w | 单词字符 [a-zA-Z0-9_] | \w+ | hello_123 | @#$ |
\W | 非单词字符 | \W | @, #, 空格 | a, 1, _ |
\s | 空白字符 | \s+ | 空格, Tab | a, 1 |
\S | 非空白字符 | \S+ | hello | 空格, Tab |
\b | 单词边界 | \bword\b | ”word" | "keyword” |
\B | 非单词边界 | \Bword | ”keyword" | "word” |
重要提示:
\d \w \s是 PCRE 特性,在grep -E(ERE)中不可用。命令行中用grep -P或用[0-9]、[a-zA-Z0-9_]、[[:space:]]替代。
2.2 字符类 [ ]
方括号 [ ] 定义一个字符集合,匹配其中任意一个字符:
# 匹配 a, e, i, o, u 中任意一个grep -E '[aeiou]' file.txt
# 匹配任意数字grep -E '[0-9]' file.txt
# 匹配任意小写字母grep -E '[a-z]' file.txt
# 匹配十六进制字符grep -E '[0-9a-fA-F]' file.txt取反 [^ ]:
# 匹配非数字字符grep -E '[^0-9]' file.txt
# 匹配非元音字母grep -E '[^aeiou]' file.txtPOSIX 字符类(命令行友好):
| POSIX 类 | 等价于 | 含义 |
|---|---|---|
[[:alpha:]] | [a-zA-Z] | 字母 |
[[:digit:]] | [0-9] | 数字 |
[[:alnum:]] | [a-zA-Z0-9] | 字母+数字 |
[[:upper:]] | [A-Z] | 大写字母 |
[[:lower:]] | [a-z] | 小写字母 |
[[:space:]] | [ \t\n\r\f\v] | 空白字符 |
[[:punct:]] | 标点符号 | !@#$% 等 |
[[:xdigit:]] | [0-9a-fA-F] | 十六进制 |
# 提取所有大写字母开头的行grep -E '^[[:upper:]]' file.txt
# 匹配包含标点符号的行grep -E '[[:punct:]]' file.txt2.3 转义字符
如果需要匹配元字符本身的字面含义,用 \ 转义:
# 匹配包含 . 的行(. 是元字符,需要转义)grep -E 'example\.com' file.txt
# 匹配包含 $ 的行grep -E 'price:\$100' file.txt
# 匹配包含方括号的行grep -E '\[error\]' file.txt
# 匹配反斜杠本身grep -E 'C:\\Windows' file.txt需要转义的元字符列表:
. * + ? ^ $ { } [ ] ( ) | \三、量词
量词控制前一个字符或组的匹配次数。
3.1 量词速查表
| 量词 | 含义 | 等价写法 | 示例 | 匹配 |
|---|---|---|---|---|
* | 0 次或多次 | {0,} | ab*c | ac, abc, abbc |
+ | 1 次或多次 | {1,} | ab+c | abc, abbc |
? | 0 次或 1 次 | {0,1} | colou?r | color, colour |
{n} | 恰好 n 次 | — | \d{3} | 123 |
{n,} | 至少 n 次 | — | \d{2,} | 12, 123, 1234 |
{n,m} | n 到 m 次 | — | \d{2,4} | 12, 123, 1234 |
# 匹配 3 位数字(如 HTTP 状态码)grep -E '[0-9]{3}' access.log
# 匹配 2-4 位数字grep -E '[0-9]{2,4}' file.txt
# 匹配 "color" 或 "colour"grep -E 'colou?r' file.txt
# 匹配一个或多个字母后跟数字grep -E '[a-z]+[0-9]+' file.txt3.2 量词的陷阱
# ❌ 错误:* 匹配 0 次,所以 a* 匹配空字符串grep -E 'a*' file.txt # 匹配所有行(包括空行)
# ✅ 正确:用 + 至少匹配一次grep -E 'a+' file.txt # 只匹配包含 a 的行
# ❌ 错误:[0-9]* 也匹配空字符串grep -E '[0-9]*' file.txt # 匹配所有行
# ✅ 正确:用 [0-9]+ 或 [0-9]{1,}grep -E '[0-9]+' file.txt规则: 永远用
+替代*当你需要”至少一个”时。*允许零次匹配,经常导致意外结果。
四、锚点与边界
锚点不匹配实际字符,而是匹配字符串中的位置。
4.1 锚点速查表
| 锚点 | 含义 | 示例 | 匹配 |
|---|---|---|---|
^ | 行/字符串开头 | ^error | 以 error 开头的行 |
$ | 行/字符串结尾 | error$ | 以 error 结尾的行 |
^...$ | 整行匹配 | ^exact$ | 只匹配 exact 这一行 |
\b | 单词边界 | \bcat\b | 匹配 cat,不匹配 category |
\B | 非单词边界 | \Bcat | 匹配 category 中的 cat |
# 匹配以 error 开头的行grep -E '^error' /var/log/syslog
# 匹配以 .com 结尾的行grep -E '\.com$' file.txt
# 匹配只有数字的行grep -E '^[0-9]+$' file.txt
# 匹配空行grep -E '^$' file.txt
# 匹配完整单词 "port"(不匹配 important, report)grep -E '\bport\b' file.txt
# 匹配包含根域名 971918 的行grep -E '\b971918\b' file.txt4.2 多行模式
默认情况下 ^ 和 $ 匹配每行的开头和结尾。在 Python 中可以用 re.MULTILINE 改变行为:
import re
text = """line 1line 2line 3"""
# 默认:^ 只匹配整个字符串开头print(re.findall(r'^line', text)) # ['line']
# 多行模式:^ 匹配每行开头print(re.findall(r'^line', text, re.MULTILINE)) # ['line', 'line', 'line']五、分组与引用
5.1 捕获组 ( )
括号 ( ) 将多个字符视为一个整体,同时捕获匹配内容供后续引用:
# 匹配 "abab" 或 "cdcd"(重复两次的模式)grep -E '([a-z]{2})\1' file.txt
# 提取日期中的年月日echo "2026-07-07" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/年:\1 月:\2 日:\3/'# 输出: 年:2026 月:07 日:07
# 交换两个字段的位置echo "John Doe" | sed -E 's/([A-Za-z]+) ([A-Za-z]+)/\2 \1/'# 输出: Doe John5.2 常见分组语法
| 语法 | 名称 | 说明 |
|---|---|---|
(pattern) | 捕获组 | 捕获匹配内容,可用 \1 或 $1 引用 |
(?:pattern) | 非捕获组 | 分组但不捕获,不占用编号 |
(?<name>pattern) | 命名捕获组 | 用名称引用,Python 写法 |
(?P<name>pattern) | 命名捕获组 | Python 特有写法 |
\1 \$1 | 反向引用 | 引用第 N 个捕获组 |
import re
# 捕获组m = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2026-07-07')print(m.group(1)) # 2026print(m.group(2)) # 07print(m.group(3)) # 07
# 命名捕获组m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-07-07')print(m.group('year')) # 2026print(m.group('month')) # 07
# 非捕获组:不保存匹配结果re.findall(r'(?:https?|ftp)://(\S+)', 'visit https://971918.xyz and http://example.com')# ['971918.xyz', 'example.com'] -- 只捕获域名部分# sed 中使用反向引用# 把 "name: value" 格式转成 "value (name)"echo "status: 200" | sed -E 's/([a-z]+): (.*)/\2 (\1)/'# 输出: 200 (status)
# 删除重复的单词echo "hello hello world" | sed -E 's/\b(\w+)\s+\1\b/\1/g'# 输出: hello world5.3 反向引用实战
# 找出连续重复的单词grep -E '\b(\w+)\s+\1\b' file.txt
# 匹配回文结构(简单版:abba)grep -E '\b(\w)(\w)\2\1\b' /usr/share/dict/words
# HTML 标签匹配(简单版)echo '<a href="url">link</a>' | sed -E 's/<a[^>]*>([^<]*)<\/a>/\1/'# 输出: link六、选择与分支
6.1 选择符 |
| 表示”或”,匹配左边或右边的模式:
# 匹配 cat 或 dog 或 birdgrep -E 'cat|dog|bird' file.txt
# 匹配 HTTP 状态码 200 或 301 或 404grep -E ' (200|301|404) ' access.log
# 匹配 http 或 httpsgrep -E 'https?' file.txt # 更简洁的写法
# 匹配常见日志级别grep -E '(ERROR|WARN|CRITICAL)' /var/log/syslog6.2 选择的优先级
| 的优先级最低,会匹配到最远处。用括号限定范围:
# ❌ 可能不是你想要的:匹配 "cat" 或 "dogfood"grep -E 'cat|dogfood' file.txt
# ✅ 匹配 "catfood" 或 "dogfood"grep -E '(cat|dog)food' file.txt
# ❌ 匹配 "http" 或 "https"grep -E 'http|https' file.txt # 实际上 "http" 就够了
# ✅ 更准确grep -E 'https?' file.txt七、贪婪与非贪婪匹配
7.1 贪婪匹配(默认)
正则引擎默认尽可能多地匹配:
文本: <div class="a">content</div>正则: <.*>结果: <div class="a">content</div> ← 匹配整个标签(到最后一个 >)7.2 非贪婪匹配
在量词后加 ? 变为尽可能少匹配:
文本: <div class="a">content</div>正则: <.*?>结果: <div class="a"> ← 只匹配到第一个 >各环境非贪婪写法:
| 环境 | 贪婪 | 非贪婪 |
|---|---|---|
| Python | .* .+ ? | .*? .+? ?? |
| JavaScript | .* .+ ? | .*? .+? ?? |
| grep/sed (ERE) | .* .+ | 不支持,用 [^>]* 替代 |
| Vim | .* | .\{-} |
# ❌ 命令行不支持非贪婪echo '<a>1</a><a>2</a>' | sed -E 's/<a>(.*)<\/a>/X/'# 输出: X ← 贪婪匹配把整个 1</a><a>2 吃掉了
# ✅ 用排除字符类替代非贪婪echo '<a>1</a><a>2</a>' | sed -E 's/<a>([^<]*)<\/a>/X/g'# 输出: XX ← 正确逐个替换import re
html = '<a href="1">link1</a><a href="2">link2</a>'
# 贪婪:匹配整个print(re.findall(r'<a.*>', html))# ['<a href="1">link1</a><a href="2">link2</a>']
# 非贪婪:逐个匹配print(re.findall(r'<a.*?>', html))# ['<a href="1">', '<a href="2">']
# 提取所有链接文本print(re.findall(r'<a[^>]*>(.*?)</a>', html))# ['link1', 'link2']7.3 贪婪/非贪婪选择原则
| 场景 | 推荐 | 原因 |
|---|---|---|
| 匹配到行尾 | 贪婪 .* | 高效,直接匹配到底 |
| 提取引号内内容 | 非贪婪 ".*?" | 避免跨多个引号 |
| 提取 HTML 标签 | 非贪婪 <.*?> | 逐个匹配标签 |
| 日志提取字段 | 排除类 [^ ]* | 最精确,避免贪婪问题 |
| 简单分隔内容 | 非贪婪 | 防止过度匹配 |
命令行技巧: grep/sed 不支持
*?,用[^x]*代替.*?x。例如"[^"]*"代替".*?"。
八、零宽断言(环视)
零宽断言(Lookaround)匹配的是位置而非字符,不消耗文本。这是 PCRE 的高级特性,grep/sed/awk 不支持,但 Python/JavaScript/Perl 支持。
8.1 四种断言
| 语法 | 名称 | 含义 |
|---|---|---|
(?=pattern) | 正向先行断言 | 右边必须匹配 pattern |
(?!pattern) | 负向先行断言 | 右边不能匹配 pattern |
(?<=pattern) | 正向后行断言 | 左边必须匹配 pattern |
(?<!pattern) | 负向后行断言 | 左边不能匹配 pattern |
8.2 实战示例
import re
# 正向先行断言:匹配后面跟着 "元" 的数字text = "价格100元,折扣20元,总计80元"print(re.findall(r'\d+(?=元)', text))# ['100', '20', '80']
# 负向先行断言:匹配不以 ".txt" 结尾的文件名files = "a.txt b.log c.txt d.conf"print(re.findall(r'\S+(?!\.txt)\b', files))# 需要注意断言的位置
# 正向后行断言:匹配 "价格" 后面的数字text = "价格100,数量5,重量200"print(re.findall(r'(?<=价格)\d+', text))# ['100']
# 负向后行断言:匹配不以 "$" 开头的数字text = "$100 200 $300 400"print(re.findall(r'(?<!\$)\b\d+', text))# ['200', '400']
# 组合:提取 HTML 标签内的文本(不含标签本身)html = '<h1>Title</h1><p>Content</p>'print(re.findall(r'(?<=<\w+>).*?(?=</\w+>)', html))# ['Title', 'Content']
# 密码验证:至少8位,含大小写字母和数字password = "Abc12345"pattern = r'(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}'print(bool(re.match(pattern, password)))# True8.3 断言的用途
| 用途 | 断言写法 | 说明 |
|---|---|---|
| 提取关键词后的值 | (?<=key)\S+ | ”key |
| 排除特定前缀 | (?<!\$)\d+ | 不以 $ 开头的数字 |
| 确认后缀格式 | \w+(?=\.txt) | 以 .txt 结尾的文件名 |
| 密码复杂度验证 | 多个 (?=...) 组合 | 同时满足多种条件 |
| CSV 按逗号分割 | ,(?=(?:[^"]*"[^"]*")*[^"]*$) | 不分割引号内的逗号 |
九、模式修饰符
模式修饰符改变正则引擎的匹配行为。
| 修饰符 | Python | JavaScript | 含义 |
|---|---|---|---|
i | re.I | /pattern/i | 忽略大小写 |
m | re.M | /pattern/m | 多行模式(^$ 匹配每行) |
s | re.S | /pattern/s | . 匹配换行符 |
x | re.X | — | 允许注释和空白 |
g | — | /pattern/g | 全局匹配(所有结果) |
import re
# 忽略大小写print(re.findall(r'error', 'Error ERROR error', re.I))# ['Error', 'ERROR', 'error']
# 单行模式:. 匹配换行html = '<div>\n<p>text</p>\n</div>'print(re.findall(r'<div>(.*?)</div>', html, re.S))# ['\n<p>text</p>\n']
# 详细模式:允许注释pattern = re.compile(r""" \d{4} # 年 - # 分隔符 \d{2} # 月 - # 分隔符 \d{2} # 日""", re.VERBOSE)print(pattern.findall('2026-07-07'))# ['2026-07-07']# 命令行中的忽略大小写grep -i 'error' /var/log/syslog # 等价于 re.I
# sed 忽略大小写sed -E 's/error/ERROR/Ig' file.txt # I 修饰符十、grep/sed/awk 正则实战
10.1 grep 正则
# === 基础搜索 ===grep 'error' file.txt # BRE 模式grep -E 'error|warn' file.txt # ERE 模式(推荐)grep -P '\d{3}' file.txt # PCRE 模式(支持 \d)
# === 常用选项 ===grep -i 'error' file.txt # 忽略大小写grep -v 'error' file.txt # 反向:不包含 error 的行grep -n 'error' file.txt # 显示行号grep -c 'error' file.txt # 统计匹配行数grep -oE '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' access.log # 只输出匹配部分grep -rE 'pattern' /path/to/dir/ # 递归搜索目录grep -E 'pattern' file.txt -A 2 # 匹配行 + 后 2 行grep -E 'pattern' file.txt -B 2 # 匹配行 + 前 2 行grep -E 'pattern' file.txt -C 2 # 匹配行 + 前后各 2 行
# === 实战:Nginx 日志分析 ===# 统计访问量 Top 10 IPgrep -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log | sort | uniq -c | sort -rn | head -10
# 统计 404 请求grep -E ' 404 ' access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
# 搜索所有 5xx 错误grep -E ' 5[0-9]{2} ' access.log
# 提取所有 URLgrep -oE '"(GET|POST|PUT|DELETE) [^"]*"' access.log
# === 实战:代码搜索 ===# 查找所有 TODO 注释grep -rnE 'TODO|FIXME|HACK' --include='*.py' .
# 查找超过 120 字符的行grep -nE '^.{121,}$' *.py
# 查找未关闭的文件句柄(有 open 没有 close)grep -E 'open\(' file.py | grep -vE 'close\('10.2 sed 正则替换
# === 基础替换 ===sed 's/old/new/' file.txt # 替换每行第一个sed 's/old/new/g' file.txt # 全局替换sed -E 's/old/new/g' file.txt # ERE 模式(推荐)sed -i 's/old/new/g' file.txt # 直接修改文件sed -i.bak 's/old/new/g' file.txt # 修改前备份
# === 实战 ===# 把所有 http:// 替换成 https://sed -E 's|http://|https://|g' config.txt
# 删除行首空格sed -E 's/^[ \t]+//' file.txt
# 删除行尾空格sed -E 's/[ \t]+$//' file.txt
# 删除空行sed -E '/^$/d' file.txt
# 给行号加前缀sed -E 's/^/LINE: /' file.txt
# 把日期格式从 YYYY-MM-DD 改成 DD/MM/YYYYecho "2026-07-07" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/'# 输出: 07/07/2026
# 隐藏手机号中间四位echo "13812345678" | sed -E 's/([0-9]{3})[0-9]{4}([0-9]{4})/\1****\2/'# 输出: 138****5678
# 删除 HTML 标签echo '<p>Hello <b>World</b></p>' | sed -E 's/<[^>]+>//g'# 输出: Hello World
# 批量重命名文件扩展名for f in *.txt; do mv "$f" "$(echo "$f" | sed -E 's/\.txt$/\.md/')"done
# 多条命令组合sed -E ' s/^[ \t]+// # 删除行首空格 s/[ \t]+$// # 删除行尾空格 /^$/d # 删除空行 s/ +/ /g # 多个空格合并为一个' file.txt10.3 awk 正则匹配
# === 基础匹配 ===awk '/error/' file.txt # 匹配包含 error 的行awk '!/error/' file.txt # 不包含 error 的行awk '/^[0-9]/{print}' file.txt # 以数字开头的行
# === 按字段匹配 ===awk -F: '$3 >= 1000 {print $1}' /etc/passwd # UID >= 1000 的用户awk '$9 == 404 {print $7}' access.log # 状态码 404 的 URLawk '$9 ~ /^5[0-9][0-9]$/ {print}' access.log # 5xx 错误
# === 实战 ===# 统计每种 HTTP 状态码的数量awk '{count[$9]++} END {for (code in count) print code, count[code]}' access.log | sort
# 统计每个 IP 的访问次数awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head -10
# 计算总流量(第 10 字段求和)awk '{total += $10} END {print total/1024/1024 " MB"}' access.log
# 提取特定时间段的日志awk '$4 ~ /07:Jul:2026:1[0-2]/' access.log # 10:00-12:59 的日志
# 多条件组合awk '$9 == 404 && $7 ~ /\.png$|\.jpg$/ {print $7}' access.log10.4 三剑客配合使用
# grep + awk:搜索匹配行后提取字段grep -E ' 404 ' access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
# sed + grep:先替换再搜索sed -E 's/[0-9]+/N/g' access.log | grep -E 'N\.N\.N\.N' | head
# 三者配合:提取日志中错误频率最高的 URLgrep -E ' 5[0-9]{2} ' access.log \ | awk '{print $7}' \ | sed -E 's/\?.*$//' \ | sort | uniq -c | sort -rn | head -10十一、Vim 中的正则
Vim 有自己的正则方言,和标准 PCRE 有差异。
11.1 Vim 正则模式
| 模式 | 前缀 | 特点 |
|---|---|---|
| 魔术模式(默认) | / | 大部分元字符可用,* 等需转义 |
| 非魔术模式 | /\M | 元字符需转义 |
| 非常魔术模式 | /\v | 接近 PCRE,推荐使用 |
| 非常非魔术模式 | /\V | 纯字面匹配 |
" 推荐用 \v(very magic)模式,接近 PCRE 语法/\v\d+ " 匹配数字/\v[a-z]+ " 匹配小写字母/\v(error|warn) " 匹配 error 或 warn
" 默认魔术模式下 + 需要转义/\d\+ " 匹配数字(默认模式)11.2 Vim 正则差异表
| 功能 | PCRE | Vim 默认 | Vim \v 模式 |
|---|---|---|---|
| 数字 | \d | \d | \d |
| 单词字符 | \w | \w | \w |
| 量词 + | + | \+ | + |
| 量词 ? | ? | \? | ? |
| 量词 {n,m} | {n,m} | \{n,m} | {n,m} |
| 分组 | (...) | \(...\) | (...) |
| 选择 | | | | | | |
| 非贪婪 | *? | .\{-} | .\{-} |
| 单词边界 | \b | \< \> | \< \> |
" === 搜索 ===" 搜索单词 "error"/\<error\>
" 搜索以 error 开头的行/^error
" 搜索 3 位数字/\v\d{3}
" 搜索邮箱地址/\v[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
" 非贪婪匹配" 匹配 <a> 标签内容/\v\<a[^>]*\>.{-}\<\/a\>
" === 替换 ===" 全局替换:%s/old/new/g
" 只替换当前行:s/old/new/g
" 确认每个替换:%s/old/new/gc
" 用 \v 模式替换:%s/\v(\d{4})-(\d{2})-(\d{2})/\3\/\2\/\1/g" 2026-07-07 → 07/07/2026
" 删除行尾空格:%s/\s\+$//
" 删除空行:g/^$/d
" 每行行首加序号:let i=1 | g/^/ s//\=i.'. '/ | let i+=1
" 把多个空格合并为一个:%s/ \+/ /g十二、Python 正则实战
Python 的 re 模块使用 PCRE 引擎,功能最全。
12.1 核心函数
import re
# === re.search:搜索第一个匹配 ===m = re.search(r'\d{4}', '日期是2026年7月7日')if m: print(m.group()) # 2026 print(m.start()) # 3(匹配起始位置) print(m.end()) # 7(匹配结束位置)
# === re.match:从字符串开头匹配 ===m = re.match(r'\d+', '123abc')print(m.group()) # 123(从头匹配数字部分)
m = re.match(r'\d+', 'abc123')print(m) # None(开头不是数字)
# === re.fullmatch:整个字符串必须完全匹配 ===print(bool(re.fullmatch(r'\d{4}', '2026'))) # Trueprint(bool(re.fullmatch(r'\d{4}', '2026a'))) # False
# === re.findall:返回所有匹配 ===print(re.findall(r'\d+', 'a1b22c333'))# ['1', '22', '333']
# 返回元组(有分组时)print(re.findall(r'(\w+)@(\w+)', 'a@b c@d'))# [('a', 'b'), ('c', 'd')]
# === re.finditer:返回迭代器(更省内存)===for m in re.finditer(r'\d+', 'a1b22c333'): print(m.group(), m.span())
# === re.sub:替换 ===# 基础替换print(re.sub(r'\d+', 'N', 'a1b22c333'))# aNbNcN
# 使用回调函数def double_num(m): return str(int(m.group()) * 2)print(re.sub(r'\d+', double_num, 'a1b22c333'))# a2b44c666
# === re.split:分割 ===print(re.split(r'[,;:\s]+', 'a, b; c: d e'))# ['a', 'b', 'c', 'd', 'e']
# 保留分隔符print(re.split(r'([,;:\s]+)', 'a, b; c'))# ['a', ', ', 'b', '; ', 'c']
# === re.compile:预编译(循环中使用可提升性能)===pattern = re.compile(r'\b\w{4}\b') # 4字母单词print(pattern.findall('the quick brown fox jumps'))# ['quick', 'brown', 'jumps']12.2 Match 对象方法
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', 'Date: 2026-07-07')
m.group() # '2026-07-07' 整个匹配m.group(0) # '2026-07-07' 同上m.group(1) # '2026' 第1组m.group(2) # '07' 第2组m.group(3) # '07' 第3组m.groups() # ('2026', '07', '07') 所有分组m.start() # 6 匹配起始位置m.end() # 16 匹配结束位置m.span() # (6, 16) (起始, 结束)m.string # 'Date: 2026-07-07' 原始字符串12.3 实战示例
import re
# === 日志解析 ===log_line = '192.168.1.1 - - [07/Jul/2026:10:30:45 +0800] "GET /api/users HTTP/1.1" 200 1234'
pattern = re.compile(r''' (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) # IP \s.*?\[ ([^\]]+) # 时间 \]\s" (\w+)\s # 方法 (\S+)\s # URL [^"]*"\s (\d{3})\s # 状态码 (\d+) # 响应大小''', re.VERBOSE)
m = pattern.search(log_line)if m: print(f"IP: {m.group(1)}") print(f"Time: {m.group(2)}") print(f"Method: {m.group(3)}") print(f"URL: {m.group(4)}") print(f"Status: {m.group(5)}") print(f"Size: {m.group(6)}")
# === 数据清洗 ===# 提取文本中的所有 URLtext = '访问 https://971918.xyz 或 http://example.com/page?q=1'urls = re.findall(r'https?://[^\s<>"\']+', text)print(urls) # ['https://971918.xyz', 'http://example.com/page?q=1']
# 敏感信息脱敏text = '手机: 13812345678, 邮箱: test@example.com, 身份证: 110101199001011234'masked = re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', text)print(masked) # 手机: 138****5678, 邮箱: test@example.com, 身份证: 110****1234
# === 模板引擎(简单版)===template = 'Hello {name}, your order #{order_id} is {status}.'
def render(template, **kwargs): return re.sub(r'\{(\w+)\}', lambda m: str(kwargs.get(m.group(1), m.group(0))), template)
print(render(template, name='Alice', order_id='12345', status='shipped'))# Hello Alice, your order #12345 is shipped.
# === 密码强度验证 ===def validate_password(password): checks = [ (r'.{8,}', '至少 8 位'), (r'.*[a-z]', '至少一个小写字母'), (r'.*[A-Z]', '至少一个大写字母'), (r'.*\d', '至少一个数字'), (r'.*[!@#$%^&*]', '至少一个特殊字符'), ] for pattern, msg in checks: if not re.search(pattern, password): return False, f'密码不符合要求: {msg}' return True, '密码强度合格'
print(validate_password('Abc12345!')) # (True, '密码强度合格')print(validate_password('abc123')) # (False, '密码不符合要求: 至少 8 位')十三、JavaScript 正则
JavaScript 正则和 Python 类似但有差异。
13.1 基础语法
// 字面量创建(推荐)const re = /pattern/flags;
// 构造函数(动态生成时使用)const re = new RegExp('pattern', 'flags');
// 常用标志// g - 全局匹配// i - 忽略大小写// m - 多行模式// s - . 匹配换行(ES2018+)// u - Unicode 模式13.2 核心方法
// === String 方法 ==='2026-07-07'.match(/\d{4}/) // ['2026']'2026-07-07'.match(/\d+/g) // ['2026', '07', '07']'a1b22c333'.replace(/\d+/, 'N') // 'aNb22c333''a1b22c333'.replace(/\d+/g, 'N') // 'aNbNcN''a,b;c:d'.split(/[,;:]/) // ['a', 'b', 'c', 'd']'2026'.search(/\d{4}/) // 0(返回位置)
// === RegExp 方法 ===/\d{4}/.test('2026') // true/\d{4}/.exec('Date: 2026-07-07') // ['2026', index: 6, ...]
// === 命名捕获组(ES2018+)===const m = '2026-07-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);console.log(m.groups.year) // 2026console.log(m.groups.month) // 07console.log(m.groups.day) // 07
// === 替换回调 ==='a1b22c333'.replace(/\d+/g, (match) => match * 2)// 'a2b44c666'
// === 零宽断言(ES2018+)===// 正向后行断言'价格100'.match(/(?<=价格)\d+/) // ['100']
// 负向后行断言'$100 200'.match(/(?<!\$)\b\d+/g) // ['200']13.3 前端实战
// 表单验证const validators = { email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, phone: /^1[3-9]\d{9}$/, url: /^https?:\/\/[^\s/$.?#].[^\s]*$/, ip: /^(\d{1,3}\.){3}\d{1,3}$/, zip: /^\d{6}$/,};
function validate(value, type) { return validators[type]?.test(value) ?? false;}
console.log(validate('test@example.com', 'email')); // trueconsole.log(validate('13812345678', 'phone')); // true
// 千分位格式化'1234567890'.replace(/\B(?=(\d{3})+(?!\d))/g, ',')// '1,234,567,890'
// 驼峰转下划线'camelCaseString'.replace(/([A-Z])/g, '_$1').toLowerCase()// 'camel_case_string'
// 下划线转驼峰'snake_case_string'.replace(/_([a-z])/g, (_, c) => c.toUpperCase())// 'snakeCaseString'
// 去除 HTML 标签'<p>Hello <b>World</b></p>'.replace(/<[^>]+>/g, '')// 'Hello World'
// 提取查询参数const query = 'name=Alice&age=30&city=Beijing';const params = Object.fromEntries( [...query.matchAll(/(\w+)=(\w+)/g)].map(m => [m[1], m[2]]));// { name: 'Alice', age: '30', city: 'Beijing' }十四、常用正则模式库
14.1 网络相关
# IPv4 地址^(\d{1,3}\.){3}\d{1,3}$^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$ # 严格版
# IPv6 地址(简化版)^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
# URL^https?://[^\s/$.?#].[^\s]*$
# 域名^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$
# 端口号^([1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$
# MAC 地址^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$14.2 用户信息
# 邮箱(通用版)^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
# 邮箱(RFC 5322 严格版)^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
# 中国手机号^1[3-9]\d{9}$
# 中国身份证号(18位)^\d{17}[\dXx]$
# 中国邮政编码^\d{6}$
# 用户名(字母数字下划线,4-20位)^[a-zA-Z0-9_]{4,20}$
# 密码(至少8位,含大小写字母和数字)^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$14.3 日期时间
# 日期 YYYY-MM-DD^\d{4}-\d{2}-\d{2}$
# 日期 YYYY-MM-DD(严格,含月份校验)^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
# 时间 HH:MM:SS^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$
# ISO 8601 时间戳^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$
# 中文日期^\d{4}年\d{1,2}月\d{1,2}日$14.4 文件与代码
# 文件扩展名\.([a-zA-Z0-9]{1,5})$
# 图片文件\.(jpe?g|png|gif|webp|svg|bmp)$
# 代码注释(单行)(\/\/.*|\/\*.*\*\/|#.*)
# 十六进制颜色^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
# HTML 标签<\/?[a-zA-Z][^>]*>
# HTML 标签内容(?<=<[^>]+>).*?(?=<[^>]+>)
# JSON 键名"([^"]+)":\s
# 版本号^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$14.5 日志分析
# Nginx 日志 IP^(\d{1,3}\.){3}\d{1,3}
# Nginx 日志时间\[([^\]]+)\]
# HTTP 状态码\b[1-5]\d{2}\b
# 错误日志级别\b(ERROR|WARN|CRITICAL|FATAL)\b
# 日志中的 URL"(GET|POST|PUT|DELETE|PATCH) ([^"]*)"
# 耗时(毫秒)(\d+)ms$十五、性能优化与灾难性回溯
15.1 什么是灾难性回溯
当一个正则表达式匹配不成功的文本时,引擎会尝试所有可能的匹配路径。如果模式设计不当,路径数量会呈指数级增长,导致 CPU 100% 甚至程序卡死。
import reimport time
# ❌ 灾难性回溯示例pattern = r'(a+)+b'text = 'a' * 30 # 30个a,没有b
start = time.time()re.search(pattern, text)print(f'耗时: {time.time() - start:.2f}s') # 可能耗时几十秒!原因: (a+)+ 可以把 aaa... 拆分成无数种组合(a + aa + a…),引擎逐一尝试。
15.2 优化技巧
import re
# ❌ 1. 避免嵌套量词r'(a+)+b' # 灾难性回溯风险r'a+b' # ✅ 简化
# ❌ 2. 避免模糊的 .* 与量词组合r'.*\w+.*' # 回溯风险r'\w+' # ✅ 如果只需要匹配单词
# ❌ 3. 避免多个可选分支重叠r'(a|a)*b' # 分支重叠导致回溯r'a*b' # ✅ 简化
# ✅ 4. 使用具体字符类替代 .r'<[^>]+>' # 比 <.*> 高效得多r'"[^"]*"' # 比 ".*?" 高效得多
# ✅ 5. 使用锚点缩小搜索范围r'^\d{4}-\d{2}-\d{2}$' # 加 ^ $ 避免中间匹配
# ✅ 6. 预编译正则pattern = re.compile(r'\b\w{4}\b')for line in lines: pattern.findall(line) # 比每次 re.findall 高效
# ✅ 7. Python 3.11+ 的原子组和占有量词r'(?>a+)b' # 原子组:匹配后不回溯r'a++b' # 占有量词:a+ 后不回溯(Python 3.11+)
# ✅ 8. 超时保护(Python 3.11+)try: re.search(r'(a+)+b', 'a' * 30, timeout=1.0)except TimeoutError: print('正则匹配超时')15.3 性能对比
import reimport time
text = 'x' * 10000 + 'abc'
# ❌ 慢:模糊匹配start = time.time()re.search(r'.*abc', text)slow = time.time() - start
# ✅ 快:精确匹配start = time.time()re.search(r'abc$', text)fast = time.time() - start
print(f'慢: {slow:.6f}s, 快: {fast:.6f}s')15.4 性能优化清单
| 优化项 | 说明 | 示例 |
|---|---|---|
| 避免嵌套量词 | (a+)+ 是回溯炸弹 | a+ |
用 [^x] 替代 .*? | 排除类比非贪婪高效 | [^>]+ 替代 .*? |
| 加锚点 | 缩小搜索范围 | ^...$ |
| 预编译 | 循环中复用 | re.compile() |
| 避免捕获 | 不需要时用非捕获组 | (?:...) |
| 具体化 | 用精确字符类替代 . | [\d-] 替代 . |
| 占有量词 | 阻止回溯 | a++ |
| 超时保护 | 防止卡死 | timeout= |
十六、调试与测试工具
16.1 在线工具
| 工具 | 网址 | 特点 |
|---|---|---|
| Regex101 | regex101.com | 最强在线测试,支持多引擎 |
| RegExr | regexr.com | 实时高亮,有模式库 |
| Debuggex | debuggex.com | 可视化正则执行流程 |
| Regex Vis | regex-vis.com | 图形化构建正则 |
16.2 命令行工具
# ripgrep(比 grep 快得多)rg 'pattern' --pcre2 # PCRE2 模式rg 'pattern' -i # 忽略大小写rg 'pattern' -l # 只显示文件名rg 'pattern' -r 'replace' # 替换预览
# grep 的调试grep -E 'pattern' --color=always file.txt # 高亮匹配
# Python 交互式测试python3 -c "import retext = 'your text here'pattern = r'your pattern'for m in re.finditer(pattern, text): print(f'{m.group()} at {m.span()}')"16.3 Python 调试技巧
import re
# 1. 用 re.DEBUG 查看编译过程re.compile(r'\d{3}-\d{4}', re.DEBUG)
# 2. 逐步测试text = 'IP: 192.168.1.1, Port: 8080'
# 先测简单部分print(re.findall(r'\d+\.\d+\.\d+\.\d+', text)) # ['192.168.1.1']
# 再组合print(re.findall(r'(\d+\.\d+\.\d+\.\d+).*?(\d+)', text))# [('192.168.1.1', '8080')]
# 3. 测试边界情况test_cases = [ ('192.168.1.1', True), ('256.1.1.1', False), # 256 > 255 ('1.2.3', False), # 只有3段 ('a.b.c.d', False), # 非数字 ('192.168.1.1.5', True), # 注意:会匹配]
ip_pattern = r'\b(\d{1,3}\.){3}\d{1,3}\b'for text, expected in test_cases: result = bool(re.search(ip_pattern, text)) status = '✅' if result == expected else '❌' print(f'{status} {text:20s} → {result}')十七、各环境正则差异总表
| 功能 | grep -E (ERE) | grep -P (PCRE) | sed -E | awk | Vim \v | Python | JavaScript |
|---|---|---|---|---|---|---|---|
\d \w \s | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
+ ? {n,m} | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 选择 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
( ) 分组 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
(?: ) 非捕获 | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
(?<name>) 命名组 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅(ES2018) |
\1 反向引用 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
*? 非贪婪 | ❌ | ✅ | ❌ | ❌ | .\{-} | ✅ | ✅ |
(?=) 先行断言 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
(?<=) 后行断言 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅(ES2018) |
\b 单词边界 | ✅ | ✅ | ✅ | ✅ | \<\> | ✅ | ✅ |
POSIX [[:digit:]] | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
十八、常见问题排查
Q1: grep 匹配不到数字
# ❌ ERE 不支持 \dgrep -E '\d+' file.txt # 不工作
# ✅ 用 [0-9] 或 POSIX 类grep -E '[0-9]+' file.txtgrep -E '[[:digit:]]+' file.txt
# ✅ 或用 PCRE 模式grep -P '\d+' file.txtQ2: sed 替换中的 / 冲突
# ❌ 路径中的 / 和分隔符冲突sed 's//usr/local/bin//opt/bin/' file.txt # 报错
# ✅ 方法1:转义sed 's/\/usr\/local\/bin/\/opt\/bin/' file.txt
# ✅ 方法2:换分隔符(推荐)sed 's|/usr/local/bin|/opt/bin|' file.txtsed 's#/usr/local/bin#/opt/bin#' file.txtQ3: Python 中匹配中文
import re
# ❌ \w 不匹配中文re.findall(r'\w+', 'Hello 世界') # ['Hello']
# ✅ 方法1:用 Unicode 范围re.findall(r'[\u4e00-\u9fff]+', 'Hello 世界') # ['世界']
# ✅ 方法2:用 re.UNICODE(Python 3 默认开启)# \w 在 Python 3 中包含中文re.findall(r'\w+', 'Hello 世界', re.UNICODE) # ['Hello', '世界']
# ✅ 匹配中文标点re.findall(r'[\u3000-\u303f\uff00-\uffef]', '你好,世界!')# [',', '!']Q4: 正则太慢
import re
# ❌ 灾难性回溯re.search(r'(a+)+b', 'a' * 25) # 卡住
# ✅ 原子组(Python 3.11+)re.search(r'(?>a+)b', 'a' * 25) # 快速失败
# ✅ 占有量词(Python 3.11+)re.search(r'a++b', 'a' * 25) # 快速失败
# ✅ 超时保护try: re.search(r'(a+)+b', 'a' * 25, timeout=1.0)except TimeoutError: print('超时')Q5: 贪婪匹配匹配过多
# ❌ 贪婪匹配把整个 HTML 都吃了echo '<a>1</a><a>2</a>' | sed -E 's/<a>(.*)<\/a>/X/g'# 输出: X
# ✅ 用排除字符类echo '<a>1</a><a>2</a>' | sed -E 's/<a>([^<]*)<\/a>/X/g'# 输出: XX# Python 中直接用非贪婪import rehtml = '<a>1</a><a>2</a>'print(re.sub(r'<a>(.*?)</a>', 'X', html)) # XXQ6: 多行匹配问题
import re
text = """<div> content</div>"""
# ❌ 默认 . 不匹配换行print(re.findall(r'<div>(.*?)</div>', text)) # []
# ✅ 用 re.S / re.DOTALLprint(re.findall(r'<div>(.*?)</div>', text, re.S)) # ['\n content\n']Q7: 特殊字符转义
import re
# 需要匹配的字面文本包含大量元字符literal = r'(a+b)*[c\d]^$'
# ❌ 手动转义容易遗漏escaped = literal.replace('(', '\\(').replace(')', '\\)')...
# ✅ 用 re.escape()pattern = re.escape(literal)print(pattern) # \(a\+b\)\*\[c\\d\]\^\$print(re.search(pattern, literal)) # 匹配成功Q8: grep -P 不可用
# 某些系统(如 macOS)grep 不支持 -Pgrep -P '\d+' file.txt # grep: invalid option -- P
# ✅ 替代方案# 方案1:用 ERE + 字符类grep -E '[0-9]+' file.txt
# 方案2:安装 GNU grepbrew install grep # macOSggrep -P '\d+' file.txt
# 方案3:用 ripgreprg '\d+' file.txt
# 方案4:用 perlperl -ne 'print if /\d+/' file.txt
# 方案5:用 awkawk '/[0-9]+/' file.txt十九、速查表
19.1 元字符速查
. 任意单个字符(除换行)\w 字母数字下划线 \W 相反\d 数字 \D 相反\s 空白字符 \S 相反\b 单词边界 \B 相反^ 行首$ 行尾\ 转义19.2 量词速查
* 0 次或多次 *? 非贪婪+ 1 次或多次 +? 非贪婪? 0 次或 1 次 ?? 非贪婪{n} 恰好 n 次{n,} 至少 n 次{n,m} n 到 m 次19.3 分组速查
(pattern) 捕获组(?:pattern) 非捕获组(?<name>pattern) 命名组(Python)(?P<name>pat) 命名组(Python 特有)\1 反向引用第 1 组(?=pattern) 正向先行断言(?!pattern) 负向先行断言(?<=pattern) 正向后行断言(?<!pattern) 负向后行断言19.4 常用命令
# grepgrep -E 'pattern' file # 扩展正则grep -P 'pattern' file # PCRE(如支持)grep -i 'pattern' file # 忽略大小写grep -v 'pattern' file # 反向匹配grep -oE 'pattern' file # 只输出匹配grep -c 'pattern' file # 计数grep -nE 'pattern' file # 行号grep -rE 'pattern' dir/ # 递归
# sedsed -E 's/old/new/g' file # 全局替换sed -E 's/old/new/gi' file # 忽略大小写sed -E '/pattern/d' file # 删除匹配行sed -E 's|old|new|g' file # 换分隔符
# awkawk '/pattern/' file # 匹配行awk '!/pattern/' file # 不匹配行awk '$1 ~ /pattern/' file # 第一列匹配awk '$1 !~ /pattern/' file # 第一列不匹配
# Pythonre.search(pattern, text) # 搜索re.match(pattern, text) # 开头匹配re.fullmatch(pattern, text) # 完全匹配re.findall(pattern, text) # 所有匹配re.sub(pattern, repl, text) # 替换re.split(pattern, text) # 分割re.compile(pattern) # 预编译19.5 Vim 正则速查
/\v\d+ " 数字(very magic)/\v\w+ " 单词/\v<a[^>]*> " HTML 标签/\v(a|b|c) " 选择/\v(\w+)\s+\1 " 重复单词:%s/\vold/new/g " 全局替换:%s/\v(\w+)/\u\1/g " 首字母大写:g/^$/d " 删除空行:%s/\s\+$// " 删除行尾空格二十、最佳实践清单
20.1 编写正则的 10 条原则
- 先写测试用例再写正则 — 列出应该匹配和不应该匹配的文本,逐一验证
- 能用字符串方法就别用正则 —
startswith()/split()/in更简单更高效 - 越具体越好 — 用
[^>]+替代.*?,用\d{4}替代\d+ - 加锚点 —
^和$不仅是精度,也是性能 - 优先用 ERE — 命令行用
grep -E,够用且兼容性好 - 注意贪婪陷阱 — 不确定时用
[^x]*替代.* - 预编译复用 — Python 循环中用
re.compile() - 转义用户输入 — 用
re.escape()处理动态模式 - 加超时保护 — Python 3.11+ 用
timeout=参数 - 写注释 — Python 用
re.VERBOSE,复杂正则必须注释
20.2 场景推荐
| 场景 | 推荐工具 | 推荐模式 |
|---|---|---|
| 快速搜索日志 | grep -E | `grep -E ‘error |
| 批量替换 | sed -E | sed -E 's/old/new/g' file |
| 字段提取 | awk | awk -F: '$3 >= 1000 {print $1}' |
| 复杂解析 | Python re | 命名捕获组 + re.VERBOSE |
| 交互编辑 | Vim | :%s/\vold/new/gc |
| 前端验证 | JavaScript | /^pattern$/ 正则字面量 |
| 大文件搜索 | ripgrep | rg 'pattern' --pcre2 |
| 测试调试 | Regex101 | 在线可视化 |
20.3 学习路径
第 1 周:基础语法 → 元字符 . \d \w \s \b → 量词 * + ? {n,m} → 锚点 ^ $ → 字符类 [ ] [^ ] → 练习:grep 搜索日志
第 2 周:分组与替换 → 捕获组 ( ) → 反向引用 \1 → sed 替换实战 → Python re.sub / re.findall
第 3 周:进阶技巧 → 非贪婪 *? +? → 非捕获组 (?:) → 零宽断言 (?=) (?<=) → 模式修饰符
第 4 周:实战与优化 → 日志分析项目 → 表单验证 → 性能优化 → 灾难性回溯防护总结
正则表达式是文本处理的瑞士军刀——Shell 脚本、Python 编程、Vim 编辑、日志分析都离不开它。
核心要点回顾:
| 知识点 | 一句话总结 |
|---|---|
| 元字符 | . \d \w \s \b 是五大基础 |
| 量词 | * + ? {n,m} 控制匹配次数 |
| 锚点 | ^ $ \b 精确定位匹配位置 |
| 分组 | ( ) 捕获、(?:) 非捕获、\1 引用 |
| 贪婪 | 默认贪婪,加 ? 变非贪婪 |
| 断言 | (?=) (?!) (?<=) (?<!) 匹配位置不消耗文本 |
| 环境 | ERE 够用于命令行,PCRE 用于编程语言 |
| 性能 | 避免 (a+)+ 嵌套量词,用 [^x] 替代 .*? |
三个关键规律:
- 具体优于模糊 —
[^>]+比.*?更精确更高效 - 简单优于复杂 — 能用
split()就别写正则 - 测试优于猜测 — 先写测试用例,再用 Regex101 验证
掌握正则表达式,你的终端工作流(Linux 命令 → Shell 脚本 → Vim 编辑 → 正则匹配)就形成了完整的闭环。从搜索一行日志到解析十万行数据,从替换单个文件到批量处理整个项目,正则表达式都是你最高效的工具。
推荐阅读
- Linux 常用命令速查手册:100+ 命令 — grep/sed/awk 命令行基础
- Shell 脚本编程完全指南 — 在脚本中使用正则表达式
- Vim 编辑器完全指南 — Vim 中的正则搜索与替换
- Nginx 反向代理配置实战 — 日志分析正则实战
- Python 类型注解完全指南 — Python 编程基础