☰
正则表达式详解:BRE、ERE、PCRE语法与实战案例
2026/10/10 14:26:37 网站建设 项目流程

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

一、三种方言先看清楚

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

方言全称谁在用特点
BREBasic Regular Expression 基本正则默认grep、sed`+ ?
EREExtended Regular Expression 扩展正则grep -E、sed -E、awk元字符直接写,不支持\d \w \s
PCREPerl Compatible Regular Expressiongrep -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\|ba\|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 '<div>hello</div><div>world</div>' | grep -oP '<div>.*?</div>'

预期输出:

<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 的人,到后来都会回来用解析器。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询