Day 10:Shell正则表达式终极指南:从“抓狂“到“掌控“的奇幻之旅
目录
- 一、正则流派之争:ERE vs PCRE
- 1. 基础语法对比表
- 2. 实战中的痛苦抉择
- 二、零宽断言:正则中的"预言家"
- 1. 四大预言术法
- 2. 实战:提取引号内内容
- 三、正则调试:从"盲猜"到"科学"
- 1. 神器推荐:regex101
- 2. Shell内置调试技巧
- 四、性能优化:正则的"涡轮增压"
- 1. 避免灾难性回溯
- 2. 减少重定向的技巧
- 3. 预编译正则(awk专属)
- 五、终极实战:日志时间戳提取器
- 课后挑战:构建正则测试框架
开篇:当你的正则表达式突然"叛变"
有没有经历过这些绝望时刻?
- 花了2小时写的正则,匹配了不该匹配的内容
- 在Stack Overflow复制的神奇正则,在你的Shell里居然失效了
- 那个.*?在grep里怎么就不工作了?
今天,我要给你一本正则表达式生存手册,让你从"正则难民"变身"模式匹配大师"!
一、正则流派之争:ERE vs PCRE
1. 基础语法对比表
特性 | ERE (grep -E) | PCRE (grep -P) |
---|---|---|
量词 | +, ?, {n,m} | 同上,支持懒惰模式+? |
分组 | () | () 和 (?😃 |
零宽断言 | 不支持 | (?=), (?<=) |
反向引用 | \1 | \1 和 \g{1} |
字符类缩写 | [[:digit:]] | \d |
2. 实战中的痛苦抉择
# 提取带千分位的数字(PCRE才能用)
echo "1,234,567" | grep -Po '\d{1,3}(?:,\d{3})*'# ERE的替代方案(更复杂)
echo "1,234,567" | grep -Eo '[[:digit:]]{1,3}(,[[:digit:]]{3})*'
二、零宽断言:正则中的"预言家"
1. 四大预言术法
# 正向先行断言(?=) - 匹配后面跟着"元"的"人民"
echo "人民币 人民教师" | grep -Po '人民(?=元)'# 负向先行断言(?!) - 匹配后面不是"币"的"人民"
echo "人民币 人民教师" | grep -Po '人民(?!币)'# 正向后行断言(?<=) - 匹配前面是"中国"的"人民"
echo "中国人民" | grep -Po '(?<=中国)人民'# 负向后行断言(?<!) - 匹配前面不是"美国"的"人民"
echo "中国人民 美国人民" | grep -Po '(?<!美国)人民'
2. 实战:提取引号内内容
# 传统方式(贪婪匹配问题)
echo '"苹果", "香蕉"' | grep -Eo '".*"' # 匹配整个"苹果", "香蕉"# 零宽断言方案(精确匹配)
echo '"苹果", "香蕉"' | grep -Po '(?<=")[^"]+(?=")'
三、正则调试:从"盲猜"到"科学"
1. 神器推荐:regex101
regex101.com 提供:
- 实时高亮匹配结果
- 详细模式解释
- 多种语言兼容性测试
2. Shell内置调试技巧
# 分步测试复杂正则
pattern='([0-9]{4})-([0-9]{2})-([0-9]{2})'
echo "2023-08-01" | grep -E "$pattern" | awk '{print "年:", $1, "月:", $2, "日:", $3
}'
四、性能优化:正则的"涡轮增压"
1. 避免灾难性回溯
# 危险正则(可能卡死)
echo "aaaaaaaaaaaaaaaaaaaa!" | grep -E '(a+)+b' # 永远找不到b# 优化方案
echo "aaaaaaaaaaaaaaaaaaaa!" | grep -E 'a+b' # 快速失败
2. 减少重定向的技巧
# 低效写法(多次管道)
cat file | grep 'A' | grep 'B' | grep 'C'# 高效方案(单次处理)
grep -E 'A.*B.*C' file# 终极优化(使用awk)
awk '/A/ && /B/ && /C/' file
3. 预编译正则(awk专属)
awk 'BEGIN{pattern="^[0-9]+$"} $1 ~ pattern' file
五、终极实战:日志时间戳提取器
#!/bin/bash
# 支持多种时间格式:
# 2023-08-01 14:00:00
# 01/Aug/2023:14:00:00 +0800
# Aug 1 14:00:00extract_timestamps() {
grep -Po \
'(?:\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})|'\
'(?:\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})|'\
'(?:\w{3} {1,2}\d{1,2} \d{2}:\d{2}:\d{2})' \
"$1" | while read -r ts; do# 统一转换为ISO格式date -d "$(echo "$ts" | sed 's#/# #g;s#:# #;s#\([A-Z][a-z]\{2\}\) \([0-9]\{2\}\)#\1 \2 #;s#+[0-9]\{4\}##;')" '+%Y-%m-%d %H:%M:%S'
done
}extract_timestamps /var/log/syslog | head -10
避坑指南:正则的"七宗罪"
- 过度贪婪:.吃掉整个文件 → 改用.?(PCRE)或[^X]*
- 编码陷阱:grep默认按字节处理 → 用LC_ALL=C grep处理二进制
- 锚点遗漏:忘记用^$导致部分匹配
- 平台差异:MacOS的grep与GNU版本不同 → 用ggrep(brew安装)
课后挑战:构建正则测试框架
需求:
- ✅ 支持测试用例文件(输入+预期输出)
- ✅ 显示失败用例的差异对比
- ✅ 统计覆盖率(测试到的正则特性)
示例输出:
$ ./regex_tester.sh email_test.txt
[PASS] 测试用例1 (标准邮箱)
[FAIL] 测试用例2 (带+号的邮箱)
输入: user+tag@example.com
预期匹配: user+tag@example.com
实际匹配: user@example.com