sed与awk-第三章-了解正则表达式语法
第三章了解正则表达式语法
表达式
正则表达式和算术表达式思想类似:
- 算术表达式:字面量+运算符,有运算优先级,算出数值结果。例:
2+3*4,乘法优先级高于加法;括号()改变计算顺序。 - 正则表达式:字面字符+元字符(特殊操作符号),描述文本模式,不是字面字符串。
字面量:写出来是什么,它的值就是什么,不需要再解析、计算。
字面字符:字符写出来是什么,就去文本中匹配一模一样的这个字符。没有特殊功能,不做任何特殊处理。
- 基础操作:字符拼接,字符依次相邻匹配。
ABE只匹配连续字符串ABE;区分大小写,A不等于a。 - 程序工作流程:grep/sed/awk读取每一行文本,拿正则模式和该行做匹配;匹配失败就向后移动字符重新尝试匹配。
简单元字符举例
.(点):通配符,匹配任意1个字符(换行除外;awk可以匹配换行);A.E匹配ACE、ABE、ALE。*(星号):修饰前面那一个单元,匹配前面单元出现0次或多次;A.*E:A后面任意数量字符,最后E。
重点区分:shell里
*本身代表任意字符;正则里*不独立工作,只修饰它前面的表达式。
两类元字符:
- 占位类元字符:自身代表一个字符,例如
. - 修饰量词元字符:不匹配字符,只修改前面的匹配规则,例如
*
成排的字符
除元字符以外的所有字符,都是字面量,匹配自身。
表3‑1:元字符汇总
BRE基础正则(grep、sed使用)
| 特殊字符 | 用途 |
|---|---|
. |
匹配除换行以外任意单个字符;awk中可以匹配换行 |
* |
匹配它前面单元0次或多次 |
[...] |
字符类,匹配括号内任意1个字符;开头写^表示否定排除;-表示范围;]放第一个位置代表字面];括号内元字符失去特殊含义 |
^ |
正则开头:匹配行首;awk匹配字符串开头 |
$ |
正则末尾:匹配行尾;awk匹配字符串结尾 |
\{n,m\} |
次数限定。\{n\}恰好n次;\{n,\}至少n次;\{n,m\}n~m次 |
\ |
转义,把元字符变为普通字符,或者把普通字符变为元字符 |
ERE扩展正则(egrep、awk使用,不需要反斜杠转义
+ ? | () {})
| 特殊字符 | 用途 |
|---|---|
+ |
前面单元出现1次或多次 |
? |
前面单元出现0次或1次 |
| |
多选“或”,匹配左边或者右边模式 |
() |
分组,把一组字符当成一个整体 |
{n,m} |
次数限定,不需要反斜杠 |
备注:传统awk对
{n,m}支持不好;GNU awk/sed完整支持。
普遍存在的反斜杠 \
反斜杠两大作用:
- 转义元字符→普通字符:如果你要匹配符号本身,加
\。- 匹配字面小数点:
\. - 匹配反斜杠本身:
\\ - 示例:匹配troff宏
.nf:\.nf
- 匹配字面小数点:
- sed中,把普通字符变成元字符:
\( \) 、\{ \} 、\n;sed必须写反斜杠,否则没有特殊含义。\1‑ \9:反向引用,第五章sed详细讲解。
通配符 .
. = 匹配任意单个字符,换行除外(awk除外)。
- 示例:
80.86,匹配80286、80386;注意也会匹配错误文本,不是只匹配数字。 - 示例:
Chapter.,匹配Chapter后面紧跟任意1个字符;- ❗行尾的Chapter不会被匹配:
.不能匹配换行;直接写Chapter可以匹配行尾。
- ❗行尾的Chapter不会被匹配:
|
|
编写正则表达式
正则写出来不生效,大多不是工具bug,而是模式描述不符合真实文本情况。
编写正则三步法
- 明确:我要匹配什么?文本中这些内容长什么样,会出现在什么上下文?
- 写模式,描述目标文本。
- 一定要测试模式,核对输出结果。
测试结果四类情况
- Hits(击中):成功匹配,正是我想要的行
- Misses(未击中):完全无关,没有匹配,正确。
- Omissions(遗漏):我想要匹配,但是没匹配到,需要扩大匹配范围。
- False alarms(假警报):不该匹配,却匹配到了,需要增加条件缩小范围。
调优思路:出现假警报 → 增加限制条件缩小匹配;出现遗漏 → 扩展模式捕获更多情况。
经验:固定字符串去掉字符,匹配范围变大;增加字符,匹配范围变小;元字符用来灵活平衡范围。
字符类 []
“类” = 一组字符的集合, 作用:指定某一个位置可以允许哪些字符,
[]整体只匹配1个字符。
示例:
[Ww]hat:首字符大写W或者小写w;匹配what/What;注意也会匹配Whatever、somewhat。\.H[12345]:匹配troff宏.H1、.H2….H5。
shell也支持
[]语法,shell会解析方括号,命令行写正则必须用单引号包裹模式,交给grep处理,不让shell提前解析。
|
|
重要规则:在
[]方括号内部,大部分元字符丧失特殊含义,.就是普通点号。
表3‑2 字符类内部特殊字符
| 字符 | 功能 |
|---|---|
\ |
仅awk内有效,用来转义字符 |
- |
不在首尾位置,代表字符范围;放在最前/最后,就是字面连字符 |
^ |
必须写在方括号第一个,代表排除(否定) |
字符的范围
用-简写连续字符:
[A‑Z]全部大写字母[0‑9]全部数字[a‑zA‑Z]大小写字母
示例:
[cC]hapter[1‑9]匹配chapter后接1位数字;会误匹配chapter 12(假警报,因为只匹配第一位数字)。- 可以同时写多个范围,混合普通字符:
[0‑9a‑z?,.;:'"] -想要作为字面符号,放到方括号第一个位置:[-+*/]匹配加减乘除符号。]右括号,放到方括号第一个位置代表字面]。
日期简单示例(不会校验日期合法性,只管格式)
|
|
排除字符类 [^...]
^写在方括号的第一个字符,代表:匹配所有不在括号内的字符(换行除外,awk可以匹配换行)。
示例:
[^0‑9]匹配任意非数字字符[^aeiou]匹配非元音字符(包含辅音、标点、大写字母)\.DS "[^1]":匹配.DS "X",X不能是数字1;用来避开.DS "1"这一行。
shell中
^有特殊含义,命令中模式务必使用单引号包裹。
POSIX字符类补充
背景:
[a‑z]只支持英文字母;POSIX为多语言设计标准字符类;GNU sed、grep、awk支持。 POSIX概念:我们说的[]字符类,标准名称叫括号表达式;POSIX三种括号内部标记:
- POSIX字符类:
[:关键字:],必须写在[]内部,例[[:alpha:]] - 整理符号
[.ch.]:GNU工具大多不支持 - 等价类
[=e=]:处理重音字符,GNU工具大多不支持
表3‑3 POSIX字符类
| 类 | 匹配字符 |
|---|---|
[:alnum:] |
字母+数字 |
[:alpha:] |
字母字符(大小写) |
[:blank:] |
空格、制表符tab |
[:cntrl:] |
控制字符 |
[:digit:] |
数字0‑9 |
[:graph:] |
可见可打印非空白字符 |
[:lower:] |
小写字母 |
[:print:] |
全部可打印字符,含空格 |
[:punct:] |
标点符号 |
[:space:] |
全部空白字符 |
[:upper:] |
大写字母 |
[:xdigit:] |
十六进制字符 |
原书提示:本书脚本不依赖POSIX字符类,兼容性考虑。
重复出现的字符(量词)
BRE基础正则(sed/grep)
*:前面单元0次或多次。[15]0*:匹配1或者5,后面0可以出现0次或者多次。- 匹配1个或多个空格:
*(空格+星号) ".*":匹配双引号之间尽可能多字符,贪婪匹配,匹配到行末尾最后一个引号。
ERE扩展正则(egrep / awk)
+:前面单元1次或多次;匹配1个及以上空格直接写+?:前面单元0次或者1次,可有可无。80[234]?86:匹配8086、80286、80386、80486。
区分shell的
?:shell问号代表任意单个字符,等价正则的.。
易错示例对比
can [no']*t:can和t之间,n/o/‘任意字符,任意次数;可以匹配cannot / can’t / cant。can.*t:can和t之间任意字符;会把肯定句I can do it也错误匹配。
术语:closure(闭合)描述“0次或多次重复”。
| 符号 | BRE (grep/sed 默认) | ERE(egrep/awk/grep‑E) | |
|---|---|---|---|
| 或 | | |
` | ` |
| 分组 | \( \) |
() |
|
| 一次或多次 | \+ |
+ |
|
| 0 或 1 次 | \? |
? |
|
| 次数范围 | \{n,m\} |
{n,m} |
只有
.* ^ $ []在两种模式下行为一样。
- BRE(Basic 基础):sed、grep 默认;
+ ? | () {}要写反斜杠\+ \? \| \( \) \{ \} - ERE(Extended 扩展):awk、egrep、
grep‑E;+ ? | () {}直接写,不加反斜杠。
单词是什么?第一部分
直接写 book(前后空格)匹配完整单词会遇到很多问题:
- 匹配不到行开头、行结尾的book。
- 单词后跟标点(
.,?),空格不存在,匹配失败。 book.*会匹配bookish、bookworm这类包含book的长单词(假警报)。
- egrep可用
book.?:book后面最多跟一个字符;可以匹配book / books;但还会匹配booky。 - sed没有
?,只能靠字符类来约束。
小结:简单空格包裹方式只能处理一小部分场景,后面第二部分给出完整方案。
定位元字符(锚点)^ $
定位符:不匹配实际字符,匹配文本的位置。
-
^:写正则最开头,匹配行首。^:匹配以tab制表符开头的行。
-
$:写正则末尾,匹配行尾。*$:匹配行末尾有若干空格的行。
-
^$:匹配空行(一行里面完全没有任何字符)1grep -c '^$' ch04 #统计文件空行数量 -
^ *$:匹配只有空格的“伪空行”。 -
^.*$:匹配整行全部内容。
重要差异:
- sed/grep(BRE):
^只在开头特殊;$只在结尾特殊;中间出现^/$就只是普通字符。 - awk:
^和$无论出现在哪里都有锚点含义;想匹配字面^ $,必须反斜杠转义\^\$。
短语
这里的短语,不是正则语法术语!是自然语言概念:指由多个单词组成的一段文字,例如
Almond Joy,两个单词构成一个短语。
grep/sed默认按行处理;模式不能跨换行匹配。 如果目标短语被换行拆分到两行,单行正则很难直接捕获。
后面sed章节会讲解多行模式,还有shell脚本处理跨行短语。
字符的跨度(次数限定大括号)
| 工具 | 语法 | 说明 |
|---|---|---|
| sed/grep(BRE) | \{n,m\} |
必须带反斜杠 |
| egrep/awk(ERE) | {n,m} |
不用反斜杠 |
参数规则:
\{n\}:严格匹配前面单元n次\{n,\}:至少n次,无上限\{n,m\}:n次~m次之间
等价对照:
*等价\{0,\}+等价\{1,\}?等价\{0,1\}
示例:
10\{2,4\}1:匹配1后面2‑4个0再跟1,匹配1001、10001,不匹配101。- 美国社保号:
[0‑9]\{3\}-[0‑9]\{2\}-[0‑9]\{4\} - 电话号码:
[0‑9]\{3\}-[0‑9]\{4\}
老版本awk不支持大括号,只能手动重复字符类写多次。
选择性操作 竖线 |(ERE扩展正则 egrep/awk)
| 代表或,两边整个模式二选一匹配。
|
|
匹配包含UNIX,或者LINUX的行。可以写多个 A|B|C。
sed基础正则BRE没有
|,要实现或逻辑,需要写多个模式。
分组操作 ()(ERE扩展正则 egrep/awk)
圆括号(),把一组字符打包成一个整体,量词、|可以对整组生效。
示例1:BigOne( Computer)?
匹配BigOne 或者 BigOne Computer;括号内整体出现0次或1次。
示例2:Lab(oratorie)?s
匹配Labs、Laboratories
|
|
示例3:compan(y|ies)
匹配company / companies。
注意:传统sed(grep BRE)不能直接用
()分组;sed要用\( \)。
单词是什么?第二部分:真正匹配完整单词
直接空格包裹的缺陷:处理不了:行首、行尾、单词前后标点符号 " ( ) ? . ,。
方案思路
- 单词前后可能出现:空格、行开头
^、行结尾$; - 单词左右可能附着标点符号:引号、括号、问号逗号句号等;
- 使用egrep的分组+或
(^| ),代表:行开头 或者 空格。
完整模式:
|
|
(^| ):book前面,要么是行开头,要么空格[\"[{(]*:book前面可以有若干左引号、左方括号、左括号
重点语法规则:字符类
[之后,如果第一个字符写的是],这个]就被当成字面右方括号,不会关闭字符类!
book:目标单词[]})\"?!.,;:'s]*:book后面可以有若干右括号、引号、标点、复数s( |$):book后面,要么空格,要么行结尾
执行命令:
|
|
vi/ex编辑器提供专门单词边界元字符:
\<单词开头,\>单词结尾;grep/sed不一定支持。
在这里替换(匹配范围,贪婪匹配)
grep只关心“整行是否匹配”;做替换的时候,要搞清楚到底哪一段文本被正则捕获。
贪婪匹配(正则默认行为)
A.*Z
.*会尽可能吃最多字符,从第一个A,匹配到这一行最后一个Z,而不是最近的Z。
示例行:
All of us, including Zippy and Ziggy and Zelda
A.*Z会匹配从A一直到最后的Zelda的Z。
工具gres(非标准unix,简化sed,书中用来演示替换逻辑),脚本实现放在原文,等价sed替换。
限制范围(解决贪婪匹配)
问题场景:引号内文本。
文本:.Se "Appendix" "Full Program Listings"
- 正则
".*"贪婪,会匹配从第一个引号一直到本行最后一个引号,把两个参数全部吞掉。 - 非贪婪思路:排除引号的字符类
[^"]:匹配任意非引号字符。 - 正确模式
"[^"]*":匹配一对引号内部最短内容。
|
|
另外案例:匹配至少5个点做引导符:
|
|
sed替换命令示例:
|
|
\( \)分组捕获,\1 \2反向引用分组内容,第五章详细讲解。
使用喜欢的元字符:表3‑4 常用正则速查表
| 项目 | 正则表达式 |
|---|---|
| 州邮政缩写 | [A‑Z][A‑Z] |
| 城市,州 | ^.*, [A‑Z][A‑Z] |
| 城市、州、邮编(egrep) | ^.*, [A‑Z][A‑Z] [0‑9]{5}(-[0‑9]{4})? |
| 月 日 年 | [A‑Z][a‑z]\{3,9\} [0‑9]\{1,2\}, [0‑9]\{4\} |
| 美国社保号 | [0‑9]\{3\}-[0‑9]\{2\}-[0‑9]\{4\} |
| 北美电话号码 | [0‑9]\{3\}-[0‑9]\{4\} |
| 美元金额 | \$[ 0‑9]*\.[0‑9][0‑9] |
| troff字体请求 | \\f([BIRP]C*[BW]*) |
| troff请求 | ^\.[a‑z]\{2\} |
| troff宏 | ^\.[A‑Z]\{2\} |
| 带参数troff宏 | ^\.[A‑Z]\{2\} .*".*" |
| HTML标签 | <[^>]*> |
| Ventura样式代码 | ^@.* = .* |
| 空行 | ^$ |
| 匹配整行 | ^.*$ |
| 1个或多个空格 | * |
本章核心要点总结
- 两套正则语法分清
- BRE基础正则:sed/grep;
+ ? | () {}需要加反斜杠转义。 - ERE扩展正则:egrep/awk;直接写
+ ? | () {}不用反斜杠。
- BRE基础正则:sed/grep;
*不独立,只修饰前面单元;.*贪婪匹配,尽量匹配最长文本;想要最短匹配用否定字符类[^xxx]。[]字符类只匹配单个字符;^放方括号第一个才是排除;-放首尾代表字面减号。- 锚点
^行首、$行尾;不匹配字符,匹配位置;awk中^ $任何位置都是特殊符号。 - 反斜杠
\:转义元字符;sed里面分组、次数大括号必须带\。 - 写正则三步:明确文本特征 → 写模式 → 必须测试;区分击中、遗漏、假警报。
- 默认按行匹配,正则不能直接跨换行;跨行需要sed高级多行功能。
- POSIX字符类
[:digit:]这类,必须嵌套在[]中,例如[[:digit:]]。
文章作者 会写代码的小郎中
上次更新 2011-08-02
许可协议 CC BY-NC-ND 4.0