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 等 |
| ` | ` | 分支 | 匹配左侧或右侧的表达式(逻辑"或"),需结合分组使用 |
三、扩展正则表达式(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
执行命令及结果:
bash
# 匹配"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
执行命令及结果:
bash
# 匹配"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
执行命令及结果:
bash
# 匹配"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(内容如下,含空行)
aa
abd
cdd
cdc
cdd
执行命令及结果:
bash
# 匹配以"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
执行命令及结果:
bash
# 匹配"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
执行命令及结果:
bash
# 匹配"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
执行命令及结果:
bash
# 匹配"l"后接0个或1个"o"再接"k"(仅"lk""lok""oooooolk"符合)
[root@rhel8 ~]# egrep "lo?k" a.txt
lk
lok
oooooolk
案例3:()将括号中的字符串作为整体匹配
操作文件 :同基础正则案例1的a.txt
执行命令及结果:
bash
# 匹配"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,再执行匹配
bash
[root@rhel8 ~]# echo labk >> a.txt
执行命令及结果:
bash
# 匹配"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)
执行命令及结果:
bash
# 匹配"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]) |