7950 字
40 分钟

正则表达式完全指南:从基础语法到多语言实战 | 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 三种正则引擎#

类型全称特点使用场景
BREBasic Regular Expression元字符需转义,功能最少grepsed 默认模式
EREExtended Regular Expression元字符直接使用,功能够用grep -Esed -Eawk
PCREPerl Compatible Regular Expression功能最全,支持断言/命名组Python re、Perl、grep -P

实用建议:

  • 命令行统一用 grep -E(ERE 足够且兼容性好)
  • 编程语言直接用语言内置 PCRE 引擎
  • 记住差异:BRE 中 + ? | () {} 需要写 \+ \? \| \(\) \{\}

1.3 第一个正则表达式#

Terminal window
# 匹配包含 "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.cabc, a1c, a cac, aBC
\d数字 [0-9](PCRE)\d{3}123, 456abc, 12
\D非数字\D+abc, !@#123
\w单词字符 [a-zA-Z0-9_]\w+hello_123@#$
\W非单词字符\W@, #, 空格a, 1, _
\s空白字符\s+空格, Taba, 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 字符类 [ ]#

方括号 [ ] 定义一个字符集合,匹配其中任意一个字符:

Terminal window
# 匹配 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

取反 [^ ]

Terminal window
# 匹配非数字字符
grep -E '[^0-9]' file.txt
# 匹配非元音字母
grep -E '[^aeiou]' file.txt

POSIX 字符类(命令行友好):

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]十六进制
Terminal window
# 提取所有大写字母开头的行
grep -E '^[[:upper:]]' file.txt
# 匹配包含标点符号的行
grep -E '[[:punct:]]' file.txt

2.3 转义字符#

如果需要匹配元字符本身的字面含义,用 \ 转义:

Terminal window
# 匹配包含 . 的行(. 是元字符,需要转义)
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*cac, abc, abbc
+1 次或多次{1,}ab+cabc, abbc
?0 次或 1 次{0,1}colou?rcolor, colour
{n}恰好 n 次\d{3}123
{n,}至少 n 次\d{2,}12, 123, 1234
{n,m}n 到 m 次\d{2,4}12, 123, 1234
Terminal window
# 匹配 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.txt

3.2 量词的陷阱#

Terminal window
# ❌ 错误:* 匹配 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
Terminal window
# 匹配以 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.txt

4.2 多行模式#

默认情况下 ^$ 匹配每行的开头和结尾。在 Python 中可以用 re.MULTILINE 改变行为:

import re
text = """line 1
line 2
line 3"""
# 默认:^ 只匹配整个字符串开头
print(re.findall(r'^line', text)) # ['line']
# 多行模式:^ 匹配每行开头
print(re.findall(r'^line', text, re.MULTILINE)) # ['line', 'line', 'line']

五、分组与引用#

5.1 捕获组 ( )#

括号 ( ) 将多个字符视为一个整体,同时捕获匹配内容供后续引用:

Terminal window
# 匹配 "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 John

5.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)) # 2026
print(m.group(2)) # 07
print(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')) # 2026
print(m.group('month')) # 07
# 非捕获组:不保存匹配结果
re.findall(r'(?:https?|ftp)://(\S+)', 'visit https://971918.xyz and http://example.com')
# ['971918.xyz', 'example.com'] -- 只捕获域名部分
Terminal window
# 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 world

5.3 反向引用实战#

Terminal window
# 找出连续重复的单词
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 选择符 |#

| 表示”或”,匹配左边或右边的模式:

Terminal window
# 匹配 cat 或 dog 或 bird
grep -E 'cat|dog|bird' file.txt
# 匹配 HTTP 状态码 200 或 301 或 404
grep -E ' (200|301|404) ' access.log
# 匹配 http 或 https
grep -E 'https?' file.txt # 更简洁的写法
# 匹配常见日志级别
grep -E '(ERROR|WARN|CRITICAL)' /var/log/syslog

6.2 选择的优先级#

| 的优先级最低,会匹配到最远处。用括号限定范围:

Terminal window
# ❌ 可能不是你想要的:匹配 "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.*.\{-}
Terminal window
# ❌ 命令行不支持非贪婪
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)))
# True

8.3 断言的用途#

用途断言写法说明
提取关键词后的值(?<=key)\S+”key” 中的 value
排除特定前缀(?<!\$)\d+不以 $ 开头的数字
确认后缀格式\w+(?=\.txt)以 .txt 结尾的文件名
密码复杂度验证多个 (?=...) 组合同时满足多种条件
CSV 按逗号分割,(?=(?:[^"]*"[^"]*")*[^"]*$)不分割引号内的逗号

九、模式修饰符#

模式修饰符改变正则引擎的匹配行为。

修饰符PythonJavaScript含义
ire.I/pattern/i忽略大小写
mre.M/pattern/m多行模式(^$ 匹配每行)
sre.S/pattern/s. 匹配换行符
xre.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']
Terminal window
# 命令行中的忽略大小写
grep -i 'error' /var/log/syslog # 等价于 re.I
# sed 忽略大小写
sed -E 's/error/ERROR/Ig' file.txt # I 修饰符

十、grep/sed/awk 正则实战#

10.1 grep 正则#

Terminal window
# === 基础搜索 ===
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 IP
grep -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
# 提取所有 URL
grep -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 正则替换#

Terminal window
# === 基础替换 ===
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/YYYY
echo "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.txt

10.3 awk 正则匹配#

Terminal window
# === 基础匹配 ===
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 的 URL
awk '$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.log

10.4 三剑客配合使用#

Terminal window
# 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
# 三者配合:提取日志中错误频率最高的 URL
grep -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 正则差异表#

功能PCREVim 默认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'))) # True
print(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)}")
# === 数据清洗 ===
# 提取文本中的所有 URL
text = '访问 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) // 2026
console.log(m.groups.month) // 07
console.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')); // true
console.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 re
import 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 re
import 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 在线工具#

工具网址特点
Regex101regex101.com最强在线测试,支持多引擎
RegExrregexr.com实时高亮,有模式库
Debuggexdebuggex.com可视化正则执行流程
Regex Visregex-vis.com图形化构建正则

16.2 命令行工具#

Terminal window
# 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 re
text = '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 -EawkVim \vPythonJavaScript
\d \w \s
+ ? {n,m}
| 选择
( ) 分组
(?: ) 非捕获
(?<name>) 命名组✅(ES2018)
\1 反向引用
*? 非贪婪.\{-}
(?=) 先行断言
(?<=) 后行断言✅(ES2018)
\b 单词边界\<\>
POSIX [[:digit:]]

十八、常见问题排查#

Q1: grep 匹配不到数字#

Terminal window
# ❌ ERE 不支持 \d
grep -E '\d+' file.txt # 不工作
# ✅ 用 [0-9] 或 POSIX 类
grep -E '[0-9]+' file.txt
grep -E '[[:digit:]]+' file.txt
# ✅ 或用 PCRE 模式
grep -P '\d+' file.txt

Q2: sed 替换中的 / 冲突#

Terminal window
# ❌ 路径中的 / 和分隔符冲突
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.txt
sed 's#/usr/local/bin#/opt/bin#' file.txt

Q3: 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: 贪婪匹配匹配过多#

Terminal window
# ❌ 贪婪匹配把整个 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 re
html = '<a>1</a><a>2</a>'
print(re.sub(r'<a>(.*?)</a>', 'X', html)) # XX

Q6: 多行匹配问题#

import re
text = """<div>
content
</div>"""
# ❌ 默认 . 不匹配换行
print(re.findall(r'<div>(.*?)</div>', text)) # []
# ✅ 用 re.S / re.DOTALL
print(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 不可用#

Terminal window
# 某些系统(如 macOS)grep 不支持 -P
grep -P '\d+' file.txt # grep: invalid option -- P
# ✅ 替代方案
# 方案1:用 ERE + 字符类
grep -E '[0-9]+' file.txt
# 方案2:安装 GNU grep
brew install grep # macOS
ggrep -P '\d+' file.txt
# 方案3:用 ripgrep
rg '\d+' file.txt
# 方案4:用 perl
perl -ne 'print if /\d+/' file.txt
# 方案5:用 awk
awk '/[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 常用命令#

Terminal window
# grep
grep -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/ # 递归
# sed
sed -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 # 换分隔符
# awk
awk '/pattern/' file # 匹配行
awk '!/pattern/' file # 不匹配行
awk '$1 ~ /pattern/' file # 第一列匹配
awk '$1 !~ /pattern/' file # 第一列不匹配
# Python
re.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 条原则#

  1. 先写测试用例再写正则 — 列出应该匹配和不应该匹配的文本,逐一验证
  2. 能用字符串方法就别用正则startswith()/split()/in 更简单更高效
  3. 越具体越好 — 用 [^>]+ 替代 .*?,用 \d{4} 替代 \d+
  4. 加锚点^$ 不仅是精度,也是性能
  5. 优先用 ERE — 命令行用 grep -E,够用且兼容性好
  6. 注意贪婪陷阱 — 不确定时用 [^x]* 替代 .*
  7. 预编译复用 — Python 循环中用 re.compile()
  8. 转义用户输入 — 用 re.escape() 处理动态模式
  9. 加超时保护 — Python 3.11+ 用 timeout= 参数
  10. 写注释 — Python 用 re.VERBOSE,复杂正则必须注释

20.2 场景推荐#

场景推荐工具推荐模式
快速搜索日志grep -E`grep -E ‘error
批量替换sed -Esed -E 's/old/new/g' file
字段提取awkawk -F: '$3 >= 1000 {print $1}'
复杂解析Python re命名捕获组 + re.VERBOSE
交互编辑Vim:%s/\vold/new/gc
前端验证JavaScript/^pattern$/ 正则字面量
大文件搜索ripgreprg '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] 替代 .*?

三个关键规律:

  1. 具体优于模糊[^>]+.*? 更精确更高效
  2. 简单优于复杂 — 能用 split() 就别写正则
  3. 测试优于猜测 — 先写测试用例,再用 Regex101 验证

掌握正则表达式,你的终端工作流(Linux 命令 → Shell 脚本 → Vim 编辑 → 正则匹配)就形成了完整的闭环。从搜索一行日志到解析十万行数据,从替换单个文件到批量处理整个项目,正则表达式都是你最高效的工具。


推荐阅读#

正则表达式完全指南:从基础语法到多语言实战 | 2026 最新实践
https://971918.xyz/posts/docs/regex-complete-guide/
作者
九所长
发布于
2026-07-07
许可协议
CC BY-NC-SA 4.0