资讯详情

资讯详情

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

正则表达式是 grep、sed、awk 共同的底层语言。前面三篇反复提到 BRE、ERE、PCRE这一篇把它们放一起讲透元字符怎么写、转义差异在哪、贪婪和非贪婪怎么回事再给几个能直接抄的实战模式。正则这东西不靠背靠对照和练。一、三种方言先看清楚Linux 上常见的正则方言有三种搞混了写出来的命令要么不匹配要么报语法错方言全称谁在用特点BREBasic Regular Expression 基本正则默认grep、sed ?EREExtended Regular Expression 扩展正则grep -E、sed -E、awk元字符直接写不支持\d \w \sPCREPerl 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/PCREabc匹配 abc、abbc不匹配 ac?前面那一项出现 0 次或 1 次ERE/PCREcolou?r匹配 color、colour[abc]字符类匹配其中任意一个[aeiou]匹配元音[^abc]取反匹配不在其中的字符[^0-9]匹配非数字[a-z]范围[A-Za-z]匹配字母(...)分组捕获ERE/PCRE 直接写BRE 要写\(...\)(ab)匹配 abab\|或ERE/PCREcat\|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\abb 重复 ≥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在脚本里更灵活。八、贪婪与非贪婪原理默认情况下正则量词是贪婪的能多吃就多吃。拿一段文本divhello/divdivworld/div模式是div.*/div。贪婪匹配.*从第一个div一直吃到最后一个/div结果是整段。非贪婪匹配.*?吃到第一个/div就停结果是divhello/div。echo divhello/divdivworld/div | grep -oP div.*?/div预期输出divhello/div divworld/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 日志提取状态码和 URLawk { 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.log11 位手机号第二位 3~9全部替换成掩码。案例 5找代码里 TODO/FIXMEgrep -rnE (TODO|FIXME|XXX): src/十、⚠️ 常见错误点号没转义。匹配192.168.1.1时写成grep 192.168.1.1点号被当成任意字符把192x168y1z1也匹配出来。匹配真实的点永远写\.。在 BRE 里写、?、|。结果要么匹配不到要么把它们当字面字符搜。养成用复杂正则就加-E的习惯。字符类写反。[0-9]是数字^[0-9]是行首是数字[^0-9]是非数字。^在[]里面和外面含义完全不同。贪婪模式翻车。提取 HTML/JSON 时用.*结果一次吞掉整段。不确定时直接加?改成非贪婪。把\d写进grep。grep \d file在大多数系统上匹配不到任何东西因为 grep 默认 BRE 不认识\d。要写grep -P \d。多行匹配问题。.默认不匹配换行。要跨行匹配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搜代码库装个rgripgrep默认 PCRE2 开关速度快 10 倍以上。JSON/HTML/XML 解析别用正则。jq、pup、xmllint这些专门的解析器比正则健壮一个数量级。用正则抠 HTML 的人到后来都会回来用解析器。正则是把利器但它不是万能。知道什么时候该放下正则比会写多复杂的正则更值钱。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →