第 6 章:正则表达式
第 6 章:正则表达式
正则表达式是文本处理的灵魂。掌握它,你就能在文字的海洋中精确捕捞。
6.1 正则表达式概览
三种正则标准
| 标准 | 全称 | 工具 | 语法严格度 |
|---|---|---|---|
| BRE | Basic Regular Expression | sed, grep(默认) | 最严格,需转义 |
| ERE | Extended Regular Expression | awk, grep -E | 较宽松 |
| PCRE | Perl-Compatible Regular Expression | grep -P, PHP, Python | 最灵活 |
BRE vs ERE 核心差异
| 功能 | BRE | ERE |
|---|---|---|
| 任意字符 | . | . |
| 零次或多次 | * | * |
| 一次或多次 | \+ | + |
| 零次或一次 | \? | ? |
| 分组 | \( \) | ( ) |
| 或 | | | | |
重复 {n,m} | \{n,m\} | {n,m} |
| 词首边界 | \b 或 \< | \b |
| 词尾边界 | \b 或 \> | \b |
💡 记忆口诀:ERE 中,元字符不需要反斜杠;BRE 中,元字符需要
\转义。但.*[ ]^$在两种标准中都不需要转义。
6.2 基础元字符
核心元字符表
| 元字符 | 含义 | BRE | ERE | 示例 |
|---|---|---|---|---|
. | 任意单个字符 | ✅ | ✅ | a.c → abc, aXc |
^ | 行首 | ✅ | ✅ | ^error |
$ | 行尾 | ✅ | ✅ | end$ |
* | 零次或多次 | ✅ | ✅ | ab*c → ac, abc, abbc |
+ | 一次或多次 | \+ | ✅ | ab+c → abc, abbc |
? | 零次或一次 | \? | ✅ | ab?c → ac, abc |
[...] | 字符类 | ✅ | ✅ | [abc] → a, b, c |
[^...] | 否定字符类 | ✅ | ✅ | [^abc] → 非 a, b, c |
(...) | 分组 | \( \) | ✅ | (ab)+ → ab, abab |
| | 或 | | | ✅ | cat|dog |
{n,m} | 重复次数 | \{n,m\} | ✅ | a{2,4} |
\n | 反向引用 | ✅ | ❌ | \(.\)\1 → aa |
\b | 单词边界 | ✅ | ✅ | \bword\b |
\w | 单词字符 | ✅ (grep) | ✅ (gawk) | [a-zA-Z0-9_] |
\d | 数字 | ❌ (PCRE) | ❌ (gawk 5.0+) | [0-9] |
\s | 空白字符 | ❌ | ✅ (gawk) | [ \t\n] |
字符类(Character Class)
# POSIX 字符类(推荐在 [] 中使用)
[[:alpha:]] # 字母 [a-zA-Z]
[[:digit:]] # 数字 [0-9]
[[:alnum:]] # 字母和数字 [a-zA-Z0-9]
[[:space:]] # 空白字符(空格、制表符、换行等)
[[:upper:]] # 大写字母
[[:lower:]] # 小写字母
[[:punct:]] # 标点符号
[[:print:]] # 可打印字符
[[:graph:]] # 可见字符(不含空格)
[[:blank:]] # 空格和制表符
[[:xdigit:]] # 十六进制字符 [0-9a-fA-F]
# 使用示例
sed -E 's/[[:space:]]+/ /g' file # 压缩多个空白为一个
awk '/[[:upper:]]/' file # 包含大写字母的行
⚠️ 注意:POSIX 字符类必须在方括号内使用:
[[:alpha:]],而不是[:alpha:]。
6.3 常用模式实战
IP 地址
# 简化匹配(0-255 精确匹配太复杂,这里用简化版)
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
# 精确匹配(0-255)(需要 PCRE 或复杂的 ERE)
# BRE 版本(简化):
/^([0-9]{1,3}\.){3}[0-9]{1,3}/
# 更精确的(匹配 0-255):
/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])/
邮箱地址
# 简化匹配
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/
# 在 awk 中使用
awk '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/ {print}' file
URL
# 匹配 HTTP/HTTPS URL
/https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[a-zA-Z0-9._~:/?#\[\]@!$&'()*+,;=-]*)?/
日期格式
# YYYY-MM-DD
/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])/
# DD/MM/YYYY
/(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/[0-9]{4}/
日志时间戳
# Apache/Nginx 格式: 15/Jan/2024:10:23:45 +0800
/\[([0-9]{2}\/[A-Z][a-z]{2}\/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4})\]/
# ISO 格式: 2024-01-15T10:23:45+08:00
/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}/
6.4 分组与反向引用
捕获组
# BRE 分组(需要转义)
sed 's/\(.*\) \(.*\)/\2 \1/' file
# ERE 分组
awk '{match($0, /([0-9]+)-([0-9]+)/, arr); print arr[1], arr[2]}' file
# SED 反向引用(BRE)
echo "abc-def" | sed 's/\(.*\)-\(.*\)/\2-\1/'
→ def-abc
非捕获组(PCRE)
# PCRE 支持非捕获组(不影响反向引用编号)
grep -P '(?:error|warning): (.*)' logfile
# 在 AWK 中,所有组都是捕获组
贪婪 vs 非贪婪
# 贪婪匹配(默认):尽可能多
echo '<b>hello</b> world' | sed -E 's/<.*>//'
→ world # 匹配了 <b>hello</b> world
# 非贪婪:尽可能少(使用 [^>]* 替代)
echo '<b>hello</b> world' | sed -E 's/<[^>]*>//g'
→ hello world
# PCRE 非贪婪量词
echo '<b>hello</b> world' | grep -Po '<.*?>'
→ <b>
→ </b>
零宽断言(Lookaround)
PCRE 和部分工具支持零宽断言:
# 正向前瞻 (?=pattern) — 后面是...
grep -P 'error(?=:)' logfile # 匹配 "error" 后面跟 ":"
# 负向前瞻 (?!pattern) — 后面不是...
grep -P 'error(?!:)' logfile # 匹配 "error" 后面不跟 ":"
# 正向后顾 (?<=pattern) — 前面是...
grep -P '(?<=error): ' logfile # 匹配 "error" 之后的 ": "
# 负向后顾 (?<!pattern) — 前面不是...
grep -P '(?<!error): ' logfile # 匹配不是 "error" 之后的 ": "
⚠️ 兼容性:零宽断言只在 PCRE(
grep -P)和部分现代工具中支持。AWK 和 SED 不支持。
6.5 性能优化
正则性能从高到低
快 ──────────────────────────────── 慢
字面字符串 > 字符类 > 量词(.*) > 反向引用 > 零宽断言
性能优化技巧
| 技巧 | 说明 | 示例 |
|---|---|---|
| 锚定 | 使用 ^ $ 减少匹配范围 | ^error 比 error 快 |
| 具体化 | 用 [a-z] 代替 . | ^[a-z]+ 比 ^.+ 快 |
| 非贪婪 | 尽早停止匹配 | [^>]* 代替 .* |
| 避免回溯 | 减少 .* 和 | | 拆分多个简单模式 |
| 短路 | 先简单后复杂 | grep -E "foo" | grep -E "complex_pattern" |
| 锚定边界 | 使用 \b | \berror\b 比 error 精确 |
# ❌ 慢:贪婪匹配 + 回溯
grep -E '.*error.*.*warning.*' logfile
# ✅ 快:两个简单 grep
grep 'error' logfile | grep 'warning'
# ❌ 慢:.* 开头
sed -E 's/.*error/error/' file
# ✅ 快:用 s/.*// 删除前缀,再处理
预编译正则(GNU AWK)
# GNU AWK 中,使用 @/pattern/ 可以"预编译"正则
gawk 'BEGIN { pattern = @/error/ } $0 ~ pattern { print }' file
# 等效但更清晰
gawk '$0 ~ /error/ { print }' file
6.6 工具间正则差异速查
| 特性 | grep (BRE) | grep -E (ERE) | grep -P (PCRE) | sed (BRE) | awk (ERE) |
|---|---|---|---|---|---|
. * [ ] ^ $ | ✅ | ✅ | ✅ | ✅ | ✅ |
+ ? { } ( ) | 转义 | ✅ | ✅ | 转义 | ✅ |
| | 转义 | ✅ | ✅ | 转义 | ✅ |
\d \w \s | ✅ | ✅ | ✅ | ❌ | 部分 |
\1 反向引用 | ✅ | ✅ | ✅ | ✅ | 仅 match() |
(?:...) 非捕获 | ❌ | ❌ | ✅ | ❌ | ❌ |
(?=...) 零宽断言 | ❌ | ❌ | ✅ | ❌ | ❌ |
*? 非贪婪 | ❌ | ❌ | ✅ | 部分 | ❌ |
(?i) 忽略大小写 | ❌ | ❌ | ✅ | ❌ | ❌ |
6.7 调试正则表达式
分步构建
# 从简单开始,逐步添加复杂度
grep 'error' logfile # 步骤 1:基本匹配
grep 'error.*[0-9]' logfile # 步骤 2:添加数字
grep -E 'error.*[0-9]{3,}' logfile # 步骤 3:添加量词
grep -E '^(.*error.*[0-9]{3,})' logfile # 步骤 4:添加锚定
使用 AWK 测试
# 测试正则是否匹配
echo "test string" | awk '/pattern/ {print "MATCH: " $0} !/pattern/ {print "NO MATCH: " $0}'
# 显示匹配位置
echo "abc123def456" | awk '{
match($0, /[0-9]+/)
print "匹配位置:", RSTART, "长度:", RLENGTH, "内容:", substr($0, RSTART, RLENGTH)
}'
SED 的 l 命令
# l 命令显示不可见字符
echo -e "tab\there" | sed -n 'l'
→ tab\there$
# 查看替换结果的可打印形式
echo "hello world" | sed 's/world/\tAWK/' | sed -n 'l'
6.8 实战练习
🏢 练习 1:提取所有链接
# 从 HTML 中提取 href 属性
grep -oP 'href="\K[^"]+' page.html
# 或用 AWK
awk '{
while (match($0, /href="([^"]+)"/, arr)) {
print arr[1]
$0 = substr($0, RSTART + RLENGTH)
}
}' page.html
🏢 练习 2:验证输入格式
# 验证日期格式 YYYY-MM-DD
echo "2024-01-15" | awk '{
if ($0 ~ /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/)
print "有效日期"
else
print "无效日期"
}'
🏢 练习 3:提取 JSON 值
# 从简单 JSON 中提取值
echo '{"name":"Alice","age":30}' | grep -oP '"name"\s*:\s*"\K[^"]+'
→ Alice
# 更复杂的场景建议使用 jq
echo '{"name":"Alice","age":30}' | jq -r '.name'
6.9 正则表达式速查卡
基本:
. 任意单字符
^ 行首
$ 行尾
* 零次或多次
+ 一次或多次 (ERE)
? 零次或一次 (ERE)
{n} 恰好 n 次 (ERE)
{n,m} n 到 m 次 (ERE)
字符类:
[abc] a, b 或 c
[^abc] 非 a, b, c
[a-z] a 到 z
[[:alpha:]] 字母
[[:digit:]] 数字
[[:space:]] 空白
边界:
^ 行首
$ 行尾
\b 单词边界
\< 词首 (BRE)
\> 词尾 (BRE)
分组:
(...) 捕获组 (ERE)
\1 反向引用
| 或 (ERE)
转义:
\. 字面量点
\* 字面量星号
\\ 字面量反斜杠
扩展阅读
- Regular-Expressions.info
- regex101.com — 在线正则测试
- PCRE2 Specification
- Mastering Regular Expressions — Jeffrey Friedl
下一章:第 7 章:文本处理实战 — 日志分析、CSV 处理、JSON 提取。