正则表达式:从日志里捞出你想要的那一行
正则不用学全,掌握十来个模式就能解决日常 90% 的文本处理。这篇只写我用得上的。
一、先选对工具
| 工具 | 正则方言 | 特点 |
|---|---|---|
grep | BRE(基础) | 默认,+?() 要转义 |
grep -E | ERE(扩展) | 推荐,+?|() 直接用 |
grep -P | PCRE | 支持 \d \b 等,功能最强 |
sed | BRE / sed -E | 替换为主 |
awk | ERE | 按字段处理更方便 |
# macOS 自带 grep 对 -P 支持不全,可用 ggrep(brew install grep)
grep -E "error|warn" app.log二、最常用的几个模式
| 模式 | 含义 | 例子 |
|---|---|---|
. | 任意单字符 | a.c 匹配 abc |
* | 前一个 0 次或多次 | ab* |
+ | 前一个 1 次或多次 | [0-9]+ |
? | 前一个 0 或 1 次 | https? |
^ $ | 行首 / 行尾 | ^GET |
[...] | 字符集 | [0-9a-f] |
() | 分组,可反向引用 | (ab)+ |
| | 或 | error|fatal |
{m,n} | 重复次数 | [0-9]{2,4} |
匹配 IP 地址
# 简单够用版(不校验 0-255 范围,日志场景足够)
grep -oE "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log
# 严格版(了解即可,太长不好念)
grep -oE "\b(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}\b"匹配时间戳
grep -E "^2026-[0-9]{2}-[0-9]{2} [0-9]{2}:" app.log
# Nginx 日志的 [19/Feb/2026:10:00:01
grep -oE "\[[0-9]{2}/[A-Za-z]{3}/[0-9]{4}:[0-9:]{8}" access.log三、grep 的实用参数
grep -E "pattern" file
grep -i # 忽略大小写
grep -v # 反向:不匹配的行
grep -n # 显示行号
grep -c # 只数有多少行
grep -o # 只输出匹配的部分(配合统计特别有用)
grep -A 3 -B 2 # 显示匹配行前后几行(看堆栈必备)
grep -r # 递归目录
grep -l # 只列出文件名
# 组合例子:统计日志里 IP 出现次数
grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}" access.log | sort | uniq -c | sort -rn | head四、sed 替换与反向引用
# 基本替换
sed -i "s/old/new/g" file
# 用 \1 \2 引用分组
echo "2026-09-17" | sed -E "s/([0-9]{4})-([0-9]{2})-([0-9]{2})/\2\/\3\/\1/"
# 09/17/2026
# 只在匹配的行做替换
sed -i "/^server_name/ s/example.com/www.bzii.cn/" nginx.conf
# 删除空行 / 注释行
sed -i "/^$/d; /^#/d" conf
# 注意:-i 直接改文件,先备份或用 -i.bak
sed -i.bak "s/old/new/" file五、awk 里用正则
# 某列匹配
awk '$9 ~ /^5[0-9]{2}$/ {print $7, $9}' access.log
# 不匹配
awk '$1 !~ /^10\./ {print}' access.log
# 按正则切分字段(默认按空白)
awk -F"[,:]"{print $1, $3}'
# 用正则做范围处理
awk '/BEGIN/,/END/ {print}' file六、实战:几个我常用的组合
# 1. 找出访问量异常高的 IP(>1000 次)
awk '{print $1}' access.log | sort | uniq -c | awk '$1 > 1000'
# 2. 找出慢请求(最后字段 request_time > 1s)
awk '$NF > 1 {print $7, $NF}' access.log
# 3. 从配置文件里抠出所有域名
grep -oE "server_name[^;]*;" /etc/nginx/sites-enabled/* | sort -u
# 4. 批量改名:把 .jpeg 改成 .jpg
for f in *.jpeg; do mv "$f" "${f%.jpeg}.jpg"; done
# 或者用 rename(正则版)
rename "s/\.jpeg$/.jpg/" *.jpeg
# 5. 去掉 ANSI 颜色转义符(日志里带 时)
sed -r "s/\x1B\[[0-9;]*[mGK]//g" log
# 6. 提取 markdown 里所有链接
grep -oE "\]\([^)]+\)" README.md七、容易踩的坑
- 贪婪匹配:
.*会尽可能多吃,用.*?(PCRE)或[^"]*限定;
grep -oE "src=\"[^\"]*\"" page.html # 正确:非引号字符
grep -oE "src=\".*\"" page.html # 错误:会吃到行尾最后一个引号- 点号要转义:匹配字面量的
.写\.; - shell 引号:正则里有
$、*、\时用单引号包裹,否则 shell 会先展开; - 中文匹配:
[\u4e00-\u9fa5]在 PCRE 里可用,grep 默认不支持,改用[^\x00-\x7F]或直接匹配具体字符。
# 找含中文的行
grep -P "[\x{4e00}-\x{9fa5}]" file八、验证正则
# 先小范围试,别直接 -i
grep -E "pattern" file | head -5
# sed 先不加 -i 看输出
sed -E "s/old/new/g" file | head
# 在线验证:regex101.com(选对方言)我的习惯:先 grep 确认匹配范围对,再动 sed -i。 直接用 -i 改生产配置是事故高发动作。
正则不用背,存一份自己的「常用模式清单」,用的时候抄。
