正则表达式详解:BRE、ERE、PCRE语法与实战案例

正则表达式是 grep、sed、awk 共同的"底层语言"。前面三篇反复提到 BRE、ERE、PCRE,这一篇把它们放一起讲透:元字符怎么写、转义差异在哪、贪婪和非贪婪怎么回事,再给几个能直接抄的实战模式。正则这东西不靠背,靠对照和练。

一、三种方言先看清楚

Linux 上常见的正则方言有三种,搞混了写出来的命令要么不匹配,要么报语法错:

方言 全称 谁在用 特点
BRE Basic Regular Expression 基本正则 默认 grep、sed `+ ?
ERE Extended Regular Expression 扩展正则 grep -E、sed -E、awk 元字符直接写,不支持 \d \w \s
PCRE Perl Compatible Regular Expression grep -P、rg、python -c、大多数现代语言 支持 \d \w \s \b、非贪婪、零宽断言

一个直观对比:匹配"一个或多个数字"。

复制代码
grep "[0-9]\+" app.log          # BRE:+ 要加反斜杠
grep -E "[0-9]+" app.log        # ERE:直接写 +
grep -P "\d+" app.log           # PCRE:\d 最简洁

同一件事三种写法,差别就在于方言。

二、基础元字符表

元字符 含义 示例
. 任意一个字符(除换行) a.c 匹配 abc、a1c
^ 行首 ^error 匹配行首是 error
$ 行尾 log$ 匹配以 log 结尾
* 前面那一项出现 0 次或多次 ab*c 匹配 ac、abc、abbc
+ 前面那一项出现 1 次或多次(ERE/PCRE) ab+c 匹配 abc、abbc,不匹配 ac
? 前面那一项出现 0 次或 1 次(ERE/PCRE) colou?r 匹配 color、colour
[abc] 字符类,匹配其中任意一个 [aeiou] 匹配元音
[^abc] 取反,匹配不在其中的字符 [^0-9] 匹配非数字
[a-z] 范围 [A-Za-z] 匹配字母
(...) 分组,捕获(ERE/PCRE 直接写;BRE 要写 \(...\)) (ab)+ 匹配 abab
| 或(ERE/PCRE) cat|dog 匹配 cat 或 dog
\ 转义 \. 匹配真实的点

三、量词:*、+、?、{m,n}

量词决定"前面那个东西重复几次"。

写法 含义
* 0 次或多次
+ 1 次或多次
? 0 次或 1 次
{n} 正好 n 次
{n,} 至少 n 次
{n,m} n 到 m 次

BRE 下 + ? | () {} 全是字面字符,要写得加反斜杠:\+、\?、\|、\(\)、\{n,m\}。ERE 下直接写。

复制代码
grep -E "error [0-9]{3,5}" app.log     # ERE:直接写 {3,5}
grep "error [0-9]\{3,5\}" app.log      # BRE:要加反斜杠

四、分组、捕获和反向引用

把一段模式用括号包起来就是一个捕获组。后面可以用 \1、\2 引用它匹配到的内容。

比如把 employees.csv 里工号和姓名互换:

复制代码
sed -E 's/^([0-9]+),([^,]+),/\2,\1,/' employees.csv

([0-9]+) 是第 1 个分组,([^,]+) 是第 2 个分组。替换串里 \1 代表第一个分组匹配到的内容,\2 代表第二个。

只分组不捕获的写法是 (?:...),但这是 PCRE 语法,BRE/ERE 不支持。

五、BRE vs ERE 转义差异对照表

这张表是本章核心,建议收藏:

元字符 BRE 写法 ERE 写法 含义
或 a|b a|b 或 a|b 匹配 a 或 b
分组 \(ab\)+ (ab)+ 把 ab 看成整体
一次或多次 ab\+ ab+ b 重复 ≥1 次
0 次或 1 次 ab\? ab? b 出现 0 或 1 次
重复 n 次 a\{3\} a{3} a 正好 3 次
重复 n 到 m 次 a\{2,4\} a{2,4} a 重复 2~4 次
` ( ) + ? { }` 字面字符 元字符

规律一句话:BRE 里的元字符在 ERE 里直接写;BRE 想把它们当元字符用,必须加反斜杠。

写脚本时最省事的做法:只要用到任何量词和括号,直接 grep -E 或 sed -E,从源头避开转义噩梦。

六、POSIX 字符类

在 [...] 里面还有一套 POSIX 定义的字符类,跨语言一致性比 [0-9]、[a-z] 更好:

写法 等价于 含义
[[:alnum:]] [A-Za-z0-9] 字母数字
[[:alpha:]] [A-Za-z] 字母
[[:digit:]] [0-9] 数字
[[:lower:]] [a-z] 小写字母
[[:upper:]] [A-Z] 大写字母
[[:space:]] 空白字符 空格、Tab、换行
[[:punct:]] 标点符号 ---
[[:xdigit:]] [0-9a-fA-F] 十六进制字符

注意它必须写在 [] 里:[[:digit:]] 是"任意数字",而 [:digit:] 单独写只是个普通字符列表,含义完全不同。这是新手高频坑。

复制代码
grep -E "[[:digit:]]{4}" employees.csv     # 匹配连续 4 位数字

七、PCRE 扩展:grep -P 才有的东西

ERE 已经够用了,但遇到复杂需求 PCRE 才舒服:

写法 含义
\d 数字,等价 [0-9]
\D 非数字
\w 单词字符,等价 [A-Za-z0-9_]
\W 非单词字符
\s 空白(空格、Tab、换行)
\S 非空白
\b 单词边界(不占字符,只是个位置)
(?=...) 正向前瞻:后面必须匹配 ...
(?!...) 负向前瞻:后面不能匹配 ...
(?:...) 非捕获分组
*?、+? 非贪婪量词

示例:用 \b 做全词匹配

复制代码
grep -P "\berror\b" app.log

这能匹配 error、error: 这种独立单词,但不会匹配 terror、errors。grep -w 也能做类似的事,\b 在脚本里更灵活。

八、贪婪与非贪婪原理

默认情况下,正则量词是贪婪的:能多吃就多吃。

拿一段文本:<div>hello</div><div>world</div>,模式是 <div>.*</div>。

  • 贪婪匹配 :.* 从第一个 <div> 一直吃到最后一个 </div>,结果是整段。

  • 非贪婪匹配 :.*? 吃到第一个 </div> 就停,结果是 <div>hello</div>。

    echo '

    hello
    world
    ' | grep -oP '
    .*?
    '

预期输出:

复制代码
<div>hello</div>
<div>world</div>

去掉 ?,结果会变成一整行。这个差别在提取 HTML、XML、JSON 片段时是天壤之别。

非贪婪只在 PCRE 里有,ERE 不支持。

九、实战案例

案例 1:提取 IPv4 地址

复制代码
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

这个模式能抓出所有形如 192.168.1.10 的字符串。它不验证 IP 合法性(999.999.999.999 也能匹配),但日志里够用。要严格合法 IP 得写更长的模式,日志场景没必要。

案例 2:从 Nginx 日志提取状态码和 URL

复制代码
awk '{ status[$9]++ } END { for (s in status) print s, status[s] }' access.log

这其实是 awk 的活。正则单独做这事更脆,因为 Nginx 日志里 URL 里可能含引号和空格。

案例 3:邮箱匹配(够用版)

复制代码
grep -oE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' emails.txt

想做"严格符合 RFC 5322"的邮箱正则长得根本没法看,实际场景用这种宽松版就够。

案例 4:把日志里的手机号脱敏

复制代码
sed -E 's/1[3-9][0-9]{9}/1**********/g' app.log

11 位手机号(第二位 3~9)全部替换成掩码。

案例 5:找代码里 TODO/FIXME

复制代码
grep -rnE "(TODO|FIXME|XXX):" src/

十、⚠️ 常见错误

  1. 点号没转义 。匹配 192.168.1.1 时写成 grep "192.168.1.1",点号被当成"任意字符",把 192x168y1z1 也匹配出来。匹配真实的点永远写 \.。
  2. 在 BRE 里写 +、?、| 。结果要么匹配不到,要么把它们当字面字符搜。养成"用复杂正则就加 -E"的习惯。
  3. 字符类写反 。[0-9] 是数字,^[0-9] 是"行首是数字",[^0-9] 是"非数字"。^ 在 [] 里面和外面含义完全不同。
  4. 贪婪模式翻车 。提取 HTML/JSON 时用 .*,结果一次吞掉整段。不确定时直接加 ? 改成非贪婪。
  5. 把 \d 写进 grep 。grep "\d+" file 在大多数系统上匹配不到任何东西,因为 grep 默认 BRE 不认识 \d。要写 grep -P "\d+"。
  6. 多行匹配问题 。. 默认不匹配换行。要跨行匹配,PCRE 用 (?s) 打开"点匹配换行"模式,或者用 [\s\S] 代替 .。

十一、知识扩展:什么时候该跳出 grep/sed 用 PCRE 工具

正则方言这事说到底是个工程取舍:

  • 简单替换、找行 :grep、sed 默认 BRE 就够,别给自己加戏。

  • 要分组、或、量词 :直接 grep -E、sed -E。

  • 要 \d \w \b、非贪婪、零宽断言 :上 grep -P,或者干脆用 Python 一行:

    python3 -c 'import re,sys; [print(m.group()) for line in sys.stdin for m in re.finditer(r"\b\d{1,3}(?:.\d{1,3}){3}\b", line)]' < access.log

  • 搜代码库 :装个 rg(ripgrep),默认 PCRE2 开关,速度快 10 倍以上。

  • JSON/HTML/XML 解析 :别用正则。jq、pup、xmllint 这些专门的解析器比正则健壮一个数量级。用正则抠 HTML 的人,到后来都会回来用解析器。

正则是把利器,但它不是万能。知道什么时候该放下正则,比会写多复杂的正则更值钱。

相关推荐
软件架构师-叶秋1 小时前
V853方案之ubuntu开发环境搭建
linux·运维·服务器·全志·v853
yt004yt1 小时前
园区绿岛 VOC 治理,能碳一体化系统设计思路分享
大数据·运维
lzx_0021 小时前
Linux 开发工具(二) 简单认识一下编辑器 —— vim
linux·编辑器·vim
码士集团小青1 小时前
美团Tabbit实测:免费用GPT-5.5和Claude Opus 4.8,浏览器Agent自动化到底能干什么
运维·gpt·自动化
AlfredZhao1 小时前
堡垒机里 `su - oracle` 被拦截,怎么绕过限制切换用户
linux
zhangjw341 小时前
1-1 PostgreSQL 多平台环境部署:Linux|Docker|Windows|MacOS 完整实操
linux·docker·postgresql
流星白龙1 小时前
【Docker】13.容器基本操作实战
运维·docker·容器
还卿一钵无情泪11 小时前
Unsloth 微调 构建自己的大模型 没有GPU也能微调
linux·开发语言·人工智能·python·大模型·nlp·unsloth
高山有多高11 小时前
【Linux笔记】IO模型
linux