Shell 正则表达式完全指南
Shell 正则表达式完全指南
正则表达式(Regular Expression,简称 Regex)是一种用于匹配、查找和替换文本的模式语言。在 Shell 环境中,正则表达式被广泛应用于 grep
、sed
、awk
、find
等工具,是文本处理和自动化脚本编写的核心技能。本文档将从基础到进阶,全面讲解 Shell 正则表达式的语法、流派差异及实战应用。
一、Shell 正则表达式的两大流派
Shell 工具对正则表达式的支持分为 基础正则表达式(Basic Regular Expression,BRE) 和 扩展正则表达式(Extended Regular Expression,ERE) 两大流派,核心区别在于特殊字符是否需要转义,不同工具对流派的支持不同,需重点区分。
1.1 流派核心差异对比
特性 | 基础正则表达式(BRE) | 扩展正则表达式(ERE) |
---|---|---|
支持工具 | grep (默认)、sed (默认)、vi | grep -E /egrep 、sed -E 、awk |
特殊字符转义要求 | ? 、+ 、{} 、() 、` | ` 需转义 |
核心场景 | 简单匹配(如固定字符串、单字符通配) | 复杂匹配(如重复次数、分支、分组) |
1.2 工具与流派对应关系
工具 | 默认流派 | 启用 ERE 的方式 | 说明 |
---|---|---|---|
grep | BRE | grep -E 或 egrep | egrep 是 grep -E 的别名(部分系统) |
sed | BRE | sed -E (GNU sed) | BSD sed(如 macOS)需用 sed -E |
awk | ERE | 无需额外参数 | 原生支持扩展语法,无需转义特殊字符 |
find | BRE(有限支持) | 无直接 ERE 选项 | 仅支持基础元字符,复杂匹配需结合 grep |
二、基础正则表达式(BRE)语法
BRE 是 Shell 工具的默认正则流派,语法简洁,适用于简单文本匹配。以下是 BRE 的核心语法,按功能分类说明。
2.1 锚定符:定位匹配位置
锚定符不匹配具体字符,仅限定匹配内容在文本中的位置,是精准匹配的关键。
符号 | 名称 | 功能说明 | 示例 |
---|---|---|---|
^ | 行首锚定 | 匹配以指定内容开头的行(仅匹配行首第一个字符后的数据) | grep '^root' /etc/passwd → 匹配所有以 root 开头的行 |
$ | 行尾锚定 | 匹配以指定内容结尾的行(仅匹配行尾最后一个字符前的数据) | grep 'bash$' /etc/passwd → 匹配所有以 bash 结尾的行 |
^$ | 空行匹配 | 匹配无任何字符的空行(^ 行首 + $ 行尾,中间无内容) | grep '^$' test.txt → 统计 test.txt 中的空行数 |
\b | 单词边界 | 匹配单词的边界(单词指由字母、数字、下划线组成的连续字符) | grep '\bhello\b' test.txt → 仅匹配独立的单词 hello (不匹配 helloworld ) |
\B | 非单词边界 | 匹配非单词边界(与 \b 相反) | grep '\Bhello\B' test.txt → 匹配 ohellow 中的 hello |
2.2 字符匹配符:匹配单个字符
字符匹配符用于匹配单个字符,可灵活指定允许的字符范围或类型。
符号 | 名称 | 功能说明 | 示例 |
---|---|---|---|
. | 任意字符 | 匹配除换行符(\n )外的任意一个字符 | grep 'h.llo' test.txt → 匹配 hello 、hallo 、hxllo 等 |
[] | 字符集合 | 匹配集合中的任意一个字符(集合内字符无序,支持 - 表示范围) | grep 'h[aei]llo' test.txt → 仅匹配 hello 、hallo 、hillo |
[^] | 否定字符集 | 匹配不在集合中的任意一个字符(^ 在 [] 内表示否定) | grep 'h[^aei]llo' test.txt → 匹配 hxllo 、hllo (不匹配 hello ) |
[a-z] | 小写字母集 | 匹配任意一个小写英文字母(a 到 z ,需按 ASCII 顺序) | grep '[a-z]' test.txt → 匹配包含小写字母的行 |
[A-Z] | 大写字母集 | 匹配任意一个大写英文字母 | grep '[A-Z]' test.txt → 匹配包含大写字母的行 |
[0-9] | 数字集 | 匹配任意一个数字(等价于 [0123456789] ) | grep '[0-9]' test.txt → 匹配包含数字的行 |
[a-zA-Z0-9_] | 单词字符集 | 匹配任意一个字母、数字或下划线(等价于 \w ,但 BRE 中 \w 兼容性较差) | grep '[a-zA-Z0-9_]' test.txt → 匹配包含单词字符的行 |
2.3 重复匹配符:匹配多个连续字符
重复匹配符用于指定前一个字符或子表达式的重复次数,是简化多字符匹配的核心。注意:BRE 中 ?
、+
、{}
需转义。
符号 | 名称 | 功能说明 | 示例 |
---|---|---|---|
* | 零次或多次 | 匹配前一个字符0次或任意多次(尽可能多匹配,贪婪模式) | grep 'ho*' test.txt → 匹配 h 、ho 、hoo 、hooo 等 |
\? | 零次或一次 | 匹配前一个字符0次或1次(可选字符) | grep 'colou\?r' test.txt → 匹配 color (美式)和 colour (英式) |
\+ | 一次或多次 | 匹配前一个字符1次或任意多次(至少1次) | grep 'a\+' test.txt → 匹配 a 、aa 、aaa (不匹配空) |
\{n\} | 恰好n次 | 匹配前一个字符恰好n次 | grep 'a\{3\}' test.txt → 仅匹配 aaa (3个连续 a ) |
\{n,\} | 至少n次 | 匹配前一个字符至少n次 | grep 'a\{2,\}' test.txt → 匹配 aa 、aaa 、aaaa 等 |
\{n,m\} | n到m次 | 匹配前一个字符n次到m次(包含n和m) | grep 'a\{2,4\}' test.txt → 匹配 aa 、aaa 、aaaa (不匹配 a 或 aaaaa ) |
2.4 分组与分支(BRE 有限支持)
BRE 对分组和分支的支持较弱,需通过转义实现,主要用于复杂逻辑匹配。
符号 | 名称 | 功能说明 | 示例 |
---|---|---|---|
\( \) | 分组 | 将括号内的内容视为一个整体(子表达式),用于重复或引用 | grep '\(ab\)*' test.txt → 匹配 ab 、abab 、ababab 等 |
| | 分支 | 匹配左侧或右侧的表达式(逻辑“或”),需结合分组使用 | grep '\(hello|world\)' test.txt → 匹配包含 hello 或 world 的行 |
三、扩展正则表达式(ERE)语法
ERE 是 BRE 的扩展,简化了特殊字符的使用(无需转义),支持更复杂的匹配逻辑,是 grep -E
、sed -E
、awk
的默认语法。
3.1 ERE 与 BRE 的核心区别
ERE 本质是 BRE 的“语法糖”,核心差异在于特殊字符无需转义,具体对比如下:
功能 | BRE 语法(需转义) | ERE 语法(无需转义) | 示例(匹配 2-4 个 a ) |
---|---|---|---|
零次或一次 | \? | ? | BRE:grep 'a\?' test.txt ERE: grep -E 'a?' test.txt |
一次或多次 | \+ | + | BRE:grep 'a\+' test.txt ERE: grep -E 'a+' test.txt |
重复次数 | \{n,m\} | {n,m} | BRE:grep 'a\{2,4\}' test.txt ERE: grep -E 'a{2,4}' test.txt |
分组 | \( \) | ( ) | BRE:grep '\(ab\)*' test.txt ERE: grep -E '(ab)*' test.txt |
分支 | | | ` | ` |
3.2 ERE 专属高级语法
ERE 除简化 BRE 语法外,无新增核心符号,但结合工具(如 awk
)可实现更灵活的应用,例如:
- 分组引用:在
sed -E
或awk
中,可通过\1
、\2
引用分组内容(\1
表示第一个分组)。
示例:sed -E 's/(hello)/\1 world/' test.txt
→ 将hello
替换为hello world
。
四、Shell 工具正则实战案例
掌握语法后,需结合工具场景灵活应用。以下是 grep
、sed
、awk
的高频实战案例。
4.1 grep:文本搜索工具
grep
是最常用的正则匹配工具,核心功能是“搜索符合模式的行”,支持 BRE(默认)和 ERE(-E
)。
需求描述 | 命令(BRE/ERE) | 说明 |
---|---|---|
搜索以 # 开头的注释行(忽略空行) | grep '^#' /etc/httpd/conf/httpd.conf | ^# 匹配行首的 # ,排除非注释行 |
搜索包含 1-3 个数字的行 | grep -E '[0-9]{1,3}' test.txt (ERE)grep '[0-9]\{1,3\}' test.txt (BRE) | ERE 无需转义 {} ,更简洁 |
搜索包含 hello 或 world 的行 | `grep -E 'hello | world’ test.txt(ERE)<br> grep ‘hello|world’ test.txt`(BRE) |
搜索独立单词 test (不匹配 test123 ) | grep '\btest\b' test.txt (BRE/ERE 通用) | \b 是单词边界,确保 test 是独立单词 |
4.2 sed:文本替换工具
sed
是流编辑器,核心功能是“按模式修改文本”,默认使用 BRE,-E
启用 ERE。
需求描述 | 命令(BRE/ERE) | 说明 |
---|---|---|
将所有 hello 替换为 HELLO | sed 's/hello/HELLO/g' test.txt (BRE) | s/旧/新/g 是替换格式,g 表示“全局替换”(默认仅替换每行第一个匹配) |
将 color 或 colour 统一为 color | sed -E 's/colou?r/color/g' test.txt (ERE) | u? 匹配 u 0 次或 1 次,同时覆盖美式和英式拼写 |
删除所有空行 | sed '/^$/d' test.txt (BRE) | /^$/ 匹配空行,d 表示“删除该行” |
在每行开头添加 [INFO] 前缀 | sed 's/^/[INFO] /' test.txt (BRE) | ^ 匹配行首,在开头插入 [INFO] |
4.3 awk:文本分析工具
awk
是强大的文本分析工具,原生支持 ERE,核心功能是“按行处理文本字段”(默认以空格分割字段,$1
表示第一个字段,$0
表示整行)。
需求描述 | 命令(ERE) | 说明 |
---|---|---|
提取 /etc/passwd 中 UID 为 0 的用户(root 及等效用户) | awk -F ':' '$3 == 0 {print $1}' /etc/passwd | -F ':' 指定分隔符为 : ,$3 是 UID 字段,匹配 UID=0 并打印用户名($1 ) |
统计日志中包含 ERROR 的行数 | awk '/ERROR/ {count++} END {print count}' log.txt | /ERROR/ 用 ERE 匹配包含 ERROR 的行,count++ 计数,END 块输出结果 |
提取 URL 中的域名(如 https://www.baidu.com/path → www.baidu.com ) | awk -F '[/:]' '/^https:\/\// {print $4}' urls.txt | -F '[/:]' 指定分隔符为 / 或 : ,$4 对应域名字段 |
五、常见问题与注意事项
5.1 转义字符的坑
-
Shell 转义 vs 正则转义:Shell 会先解析命令中的特殊字符(如
$
、*
、\
),再将结果传递给正则工具。若需匹配正则中的特殊字符(如$
行尾锚定),需避免被 Shell 解析。
解决方案:用单引号包裹正则表达式(Shell 不解析单引号内的字符),例如grep '^hello$' test.txt
(正确匹配整行hello
),而非双引号(grep "^hello$" test.txt
可能被 Shell 干扰)。 -
工具差异:BSD sed(macOS)和 GNU sed(Linux)对转义的处理略有不同,例如 macOS 中
sed
需用sed -E
启用 ERE,而 Linux 中sed -r
与sed -E
等价。
5.2 贪婪匹配与非贪婪匹配
Shell 正则默认是贪婪模式(尽可能匹配最长的内容),例如 grep 'a.*b' test.txt
会匹配 a1b2b
中的 a1b2b
(而非 a1b
)。
注意:Shell 正则(BRE/ERE)不支持非贪婪模式(如 .*?
),若需非贪婪匹配,需使用 perl
或 grep -P
(部分系统支持,启用 Perl 兼容正则),例如 grep -P 'a.*?b' test.txt
。
5.3 空行与空白行的区别
- 空行:无任何字符(包括空格、制表符),正则为
^$
。 - 空白行:包含空格或制表符(
\t
),正则为^[[:space:]]*$
([[:space:]]
匹配任意空白字符,*
表示零次或多次)。
示例:grep '^[[:space:]]*$' test.txt
→ 匹配空行和空白行。
六、总结
Shell 正则表达式是文本处理的核心工具,需重点掌握:
- 流派区分:BRE(默认,需转义特殊字符)和 ERE(
-E
启用,无需转义)。 - 核心语法:锚定符(
^
、$
)、字符匹配符(.
、[]
)、重复匹配符(*
、{n,m}
)、分组与分支(()
、|
)。 - 工具实战:
grep
搜索、sed
替换、awk
字段分析,结合具体场景选择合适工具。
通过多练习文本处理场景(如日志分析、配置文件修改、数据提取),可快速掌握正则表达式的灵活应用。
正则表达式练习题
2. 基础正则元字符实战案例(基于grep
工具)
案例1:*
匹配前面一个字符0次或多次
操作文件:a.txt
(内容如下)
lk
lok
look
loook
looooook
loooooaaak
looooooook
abbbbcd
abbbbcd666
ooooloooook
oooooolk
aoblck
执行命令及结果:
# 匹配“lo”后接0个或多个“o”再接“k”(即“lok”“look”“loook”等,排除“lk”)
[root@rhel8 ~]# grep "loo*k" a.txt
lok
look
loook
looooook
looooooook
ooooloooook# 匹配“l”后接0个或多个“o”再接“k”(包含“lk”“lok”“look”等)
[root@rhel8 ~]# grep "lo*k" a.txt
lk
lok
look
loook
looooook
looooooook
ooooloooook
oooooolk
案例2:.
匹配除\n
之外的任意一个字符
操作文件:同案例1的a.txt
执行命令及结果:
# 匹配“lo”后接任意字符(0个或多个)再接“k”(包含中间有其他字符的情况,如“loooooaaak”)
[root@rhel8 ~]# grep "lo.*k" a.txt
lok
look
loook
looooook
loooooaaak
looooooook
ooooloooook# 重复执行上述命令,结果一致
[root@rhel8 ~]# grep "lo.*k" a.txt
lok
look
loook
looooook
loooooaaak
looooooook
ooooloooook# 匹配“lo”后接1个任意字符再接“k”(仅“look”符合)
[root@rhel8 ~]# grep "lo.k" a.txt
look# 匹配“l”后接2个任意字符再接“k”(仅“look”符合,“l”+“oo”+“k”)
[root@rhel8 ~]# grep "l..k" a.txt
look
案例3:\{n\}
、\{n,\}
、\{n,m\}
匹配字符重复次数
操作文件:同案例1的a.txt
执行命令及结果:
# 匹配“lo”后接2个“o”再接“k”(即“look”,“o”恰好出现2次)
[root@rhel8 ~]# grep "lo\{2\}k" a.txt
look# 匹配“lo”后接3个“o”再接“k”(即“loook”,“o”恰好出现3次)
[root@rhel8 ~]# grep "lo\{3\}k" a.txt
loook# 匹配“lo”后接3个及以上“o”再接“k”(“loook”“looooook”等)
[root@rhel8 ~]# grep "lo\{3,\}k" a.txt
loook
looooook
looooooook
ooooloooook# 匹配“lo”后接3-5个“o”再接“k”(仅“loook”“ooooloooook”符合)
[root@rhel8 ~]# grep "lo\{3,5\}k" a.txt
loook
ooooloooook
案例4:^
、$
匹配字符串首尾位置
操作文件:b.txt
(内容如下,含空行)
aaabd
cdd
cdc
cdd
执行命令及结果:
# 匹配以“c”开头的字符串(“cdd”“cdc”)
[root@rhel8 ~]# grep "^c" b.txt
cdd
cdc
cdd# 匹配以“d”结尾的字符串(“abd”“cdd”)
[root@rhel8 ~]# grep "d$" b.txt
abd
cdd
cdd# 匹配空行(输出结果为空行,对应文件中的空行)
[root@rhel8 ~]# grep "^$" b.txt [root@rhel8 ~]#
案例5:[list]
、[^list]
匹配指定/非指定字符
操作文件:c.txt
(内容如下)
lok
lo12k
lo1k
loAk
loBk
look
loak
lodk
abcd
1234
执行命令及结果:
# 匹配“lo”后接字母(a-z、A-Z)或数字(0-9)再接“k”(排除“lok”“lo12k”)
[root@rhel8 ~]# grep "lo[a-zA-Z0-9]k" c.txt
lo1k
loAk
loBk
look
loak
lodk# 匹配“lo”后接“A”“B”“o”中的任意一个字符再接“k”(“loAk”“loBk”“look”)
[root@rhel8 ~]# grep "lo[ABo]k" c.txt
loAk
loBk
look# 匹配“lo”后接非字母(a-z、A-Z)的字符再接“k”(仅“lo1k”符合)
[root@rhel8 ~]# grep "lo[^a-zA-Z]k" c.txt
lo1k# 匹配包含非字母(a-z、A-Z)的字符串(“lo12k”“lo1k”“1234”)
[root@rhel8 ~]# grep "[^a-zA-Z]" c.txt
lo12k
lo1k
1234
三、扩展正则
扩展正则是基础正则的补充,支持更多灵活的匹配逻辑,核心元字符及功能如下:
元字符 | 功能说明 | 示例 |
---|---|---|
+ | 匹配前面一个字符出现1次或多次(比* 更严格,至少1次) | lo+k (匹配lo 后接1个或多个o 再接k ,如lok 、look 、loook ) |
? | 匹配前面一个字符出现0次或1次(可选字符匹配) | lo?k (匹配l 后接0个或1个o 再接k ,即lk 、lok ) |
() | 将括号中的字符串作为一个整体(分组匹配) | l(oo)+k (匹配l 后接1个或多个“oo ”整体再接k ,如look 、looook ) |
` | ` | 以“或”逻辑匹配多个字符串(分支匹配) |
{} | 为可重复的正则表达式指定重复次数(间隔匹配),功能同基础正则的\{n\} /\{n,\} /\{n,m\} ,但无需转义 | lo{2}k (匹配lo 后接2个o 再接k )、lo{2,}k (匹配lo 后接2个及以上o 再接k )、lo{2,3}k (匹配lo 后接2-3个o 再接k ) |
2. 扩展正则元字符实战案例(基于egrep
工具)
案例1:+
匹配前面一个字符1次以上
操作文件:同基础正则案例1的a.txt
执行命令及结果:
# 匹配“lo”后接1个或多个“o”再接“k”(排除“lk”,包含“lok”“look”等)
[root@rhel8 ~]# egrep "lo+k" a.txt
lok
look
loook
looooook
looooooook
ooooloooook
案例2:?
匹配前面一个字符0次或1次
操作文件:同基础正则案例1的a.txt
执行命令及结果:
# 匹配“l”后接0个或1个“o”再接“k”(仅“lk”“lok”“oooooolk”符合)
[root@rhel8 ~]# egrep "lo?k" a.txt
lk
lok
oooooolk
案例3:()
将括号中的字符串作为整体匹配
操作文件:同基础正则案例1的a.txt
执行命令及结果:
# 匹配“l”后接1个或多个“oo”整体再接“k”(“look”“looooook”“looooooook”,即“oo”重复1次、2次、3次)
[root@rhel8 ~]# egrep "l(oo)+k" a.txt
look
looooook
looooooook
案例4:|
以“或”逻辑匹配多个字符串
操作文件:先向a.txt
追加内容labk
,再执行匹配
[root@rhel8 ~]# echo labk >> a.txt
执行命令及结果:
# 匹配“l”后接1个或多个“oo”整体,或1个或多个“ab”整体,再接“k”(“look”“looooook”“looooooook”“labk”)
[root@rhel8 ~]# egrep "l(oo|ab)+k" a.txt
look
looooook
looooooook
labk
案例5:{}
指定字符重复次数
操作文件:同基础正则案例1的a.txt
(已追加labk
)
执行命令及结果:
# 匹配“lo”后接3个“o”再接“k”(仅“loook”符合)
[root@rhel8 ~]# egrep "lo{3}k" a.txt
loook# 匹配“lo”后接3个及以上“o”再接“k”(“loook”“looooook”“looooooook”“ooooloooook”)
[root@rhel8 ~]# egrep "lo{3,}k" a.txt
loook
looooook
looooooook
ooooloooook# 匹配“lo”后接3-5个“o”再接“k”(仅“loook”“ooooloooook”符合)
[root@rhel8 ~]# egrep "lo{3,5}k" a.txt
loook
ooooloooook
四、特殊的字符组
特殊字符组是基础/扩展正则中预设的“字符集合缩写”,简化对特定类型字符的匹配,适用于所有支持正则的Linux工具,具体如下:
特殊字符组 | 描述 |
---|---|
[[:alpha:]] | 匹配任意字母字符(大写A-Z或小写a-z) |
[[:alnum:]] | 匹配任意字母数字字符(0-9、A-Z、a-z) |
[[:blank:]] | 匹配空格( )或Tab键(\t ) |
[[:digit:]] | 匹配0-9之间的任意一个数字(等价于[0-9] ) |
[[:lower:]] | 匹配小写字母字符(a-z,等价于[a-z] ) |
[[:print:]] | 匹配任意可打印字符(包括字母、数字、标点、空格等,排除不可见字符如\n ) |
[[:punct:]] | 匹配任意标点符号(如! 、@ 、# 、$ 、, 、. 等) |
[[:space:]] | 匹配任意空白字符(包括空格、Tab键、换行符\n 、换页符\f 、垂直制表符\v 、回车符\r ) |
[[:upper:]] | 匹配任意大写字母字符(A-Z,等价于[A-Z] ) |