Shell 正则表达式

191 阅读5分钟

正则表达式是什么

正则表达式 (regular expression)是一些具体有特殊含义的符号,组合在一起的共同描述字符或字符串的方法,通俗来讲正则为描述同一类事物的规则,例如我们生活中描述可以飞行的是事物,则满足这条规则的可以是鸟,蝴蝶,也可以是飞机等。
在 Linux 系统中,正则表达式通常用来对字符或字符串来进行处理,它是用于描述字符排列或匹配模式的一种语言规则。

为什么要用正则表达式

我们知道正则表达式是一个描述字符排列或模式匹配的规则,我们可以利用它来制定自己的规则,获取到我们想要的结果等。在后续的 Shell 三剑客 grep/awk/sed Shell 的学习中,我们会结合正则表达式与这些命令进行结合使用,来实现更强大的文本处理功能。正则表达式是我们 Shell 学习的核心也是难点,在 Linux 中一切皆文件,多文件的处理可以覆盖我们日常工作的 90%,所有熟练掌握正则表达式显得尤为重要,在之后灵活配合其他命令可以非常方便的满足我们的日常处理需求。

正则表达操作

正则表达式常见的有两种分类:

基本正则表达式:Basic Regular Expression 又叫 Basic RegEx 简称 BRE。基础正则表达式元字符有:^$.*{}、、[]\<\>

扩展正则表达式:Extended Regular Expression 又叫 Extended RegEx 简称 ERE。拓展正则表达式元字符有:+?()|+

注意(){}使用的时候需要用\来转义,如:3\{5\}来匹配5个3

1、基础正则表达式元字符前导符可以是字符或子表达式

  • ^ : (每一行)行首定位符
  • $ : (每一行)行尾定位符
  • . : 匹配单个字符
  • * : 匹配前导符0次或多次
  • .* : 任意多个字符
  • [] : 匹配指定范围内的一个字符
  • [-] : 匹配指定连续范围内的一个字符
  • [^] : 匹配不在指定组内的字符
  • \ : 转义字符(脱意符),用于取消元字符的特殊意义或引用特殊字符
  • \< : (每个单词)词首定位符,可与\>配合使用
  • \> : (每个单词)词尾定位符,可与\<配合使用

2、推展正则表达式元字符

  • + : 匹配前导符一次或多次
  • ? : 匹配前导符0次或1次
  • | : 表示逻辑"或"
  • () : 组字符,用于分组
  • {} : 可当做一个量词
    • x\{m\} : 前导符x重复出现m次
    • x\{m,\} : 前导符x重复出现m次以上
    • x\{m,n\} : 前导符x重复出现m到n次

3、POSIX字符(不常用)

POSIX 称为:Portable Operating System Interface(末尾增加 X 只是为了更流畅)的缩写

:<< EOF
通用 POSIX 原字符如下:
[:alnum:] 匹配字母数字,等价于[a-zA-Z0-9]
[:alpha:] 匹配字母,等价于[a-zA-Z]
[:digit:] 匹配数字,等价于[0-9]
[:lower:] 匹配小写,等价于[a-z]
[:upper:] 匹配大写,等价于[A-Z]
[:space:] 匹配任意空白字符(包括空格、制表符、换行符等),等价于[ \t\n\r\f\v],
[:blank:] 匹配空格或制表键,等价于[ \t],
[:cntrl:] 匹配任何控制字符(ASCII 值在 0 到 31 之间的字符)
[:graph:] 匹配可打印字符(不包括空白字符)
[:print:] 匹配可打印字符(包括空白字符)
[:punct:] 匹配标点符号
[:xdigit:] 匹配十六进制数字,等价于[0-9a-fA-F]
EOF

# 示例
grep "r[[:alnum:]]+t" re.txt # 匹配"r" 与 "t"之间有1个或多个 字母或数字字符 的行
grep "r^[[:alpha:]]+t" re.txt # 取反的情况,匹配"r" 与 "t"之间有1个或多个 非字母字符 的行

4、特殊符号(不常用)

:<< EOF
在扩展正则表达式中加上 \ 则被认为其具有特殊含义:
\w: 匹配任意数字和字母,等效[a-zA-Z0-9_]
\W: 和\w相反,等效[^a-zA-Z0-9_]
\b: 匹配字符串开始或结束,等效\<和\>
\s: 匹配任意的空白字符
\S: 匹配非空白字符
\d: 匹配任意数字(等同于 [0-9])
\D: 匹配任意非数字(等同于 [^0-9])
(?=pattern): 正向前瞻,匹配后面的内容但不捕获,`pattern`是需要匹配的模式
(?!pattern): 负向前瞻,匹配后面不包含的内容
(?<=pattern): 正向后顾,匹配前面的内容但不捕获
(?<!pattern): 负向后顾,匹配前面不包含的内容
EOF

# 正向前瞻示例:包含大小写字母,数字长度至少是8位的密码:
brew install grep # MacOS默认不支持前瞻,需要安装,其他系统这步跳过
passwd="Aa123456"
echo "$password" | ggrep -P '^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$' && echo "密码符合规则" || echo "密码不符合规则"
:<< EOF
(?=.*[A-Z]):正向肯定前瞻(Positive Lookahead)。确保字符串中至少包含一个大写字母。
.*:表示任意数量的任意字符(包括零个字符),直到遇到一个大写字母。
[A-Z]:表示大写字母
EOF


# 示例
grep "r\w+t" re.txt # 匹配"r" 与 "t"之间有1个或多个 字母或数字字符 的行
基本正则表达式(BRE)示例
# 匹配包含 "root" 的行
grep "root" /etc/passwd

# 匹配以 "root" 开头的行
grep "^root" /etc/passwd

# 匹配以 "root" 结尾的行
grep "root$" /etc/passwd

# 匹配包含 "r" 后跟任意字符再跟 "o" 的行
grep "r.o" /etc/passwd

# 匹配包含零个或多个 "r" 再跟 "o" 的行
grep "r*o" /etc/passwd

# 匹配包含"r" 与 "t" 之间有任意多个字符的行
grep "r.*t" /etc/passwd # 比如:rt、rot等

# 匹配包含 "r" 后跟 "o" 或 "a" 的行
grep "r[oa]" /etc/passwd

# 匹配包含 "r" 后跟 "o" 到 "z" 范围内的任意字符的行
grep "r[o-z]" /etc/passwd # grep 'r[A-Za-z0-9]' /etc/passwd

# 匹配包含 "r" 后跟字母或数字的行
grep "r[A-Za-z0-9]" /etc/passwd

# 匹配包含 "r" 后跟 非大写字母的字符的行
grep "r[^A-Z]" /etc/passwd

# 匹配包含 "r.oot" 的行
grep "r\.oot" /etc/passwd

# 匹配包含 以"un"开始的单词 的行
grep "\<un" /etc/passwd

# 匹配包含 以"ed"结尾的单词 的行
grep "\<ed" /etc/passwd

# 匹配包含 root 单词 的行
grep "\<root\>" /etc/passwd # 用\b的等价命令:grep "\broot\b" /etc/passwd
拓展正则表达式(ERE)示例
# grep用到ERE两种方式:
# 方式一:
egrep "r?o" /etc/passwd
# 方式二:(这里统一用方式二)
grep -E "r?o" /etc/passwd

# 匹配包含 1个或多个 "r" 再跟 "o" 的行,比如:ro,rro,……
grep -E "r+o" /etc/passwd

# 匹配包含 零个或一个 "r" 再跟 "o" 的行,比如:o,ro,root,……
grep -E "r?o" /etc/passwd

# 匹配包含 零个或一个 "l" 与 "ve" 之间是"o"或"a",比如:love或lave
grep -E "l(o|a)ve" re.txt

# 匹配包含 零个或一个 "l" 与 "ve" 之间是"o"或"a",比如:love或lave
grep -E "l(o|a)ve" re.txt

# 匹配包含 "r"与"t"之间有2个"o" 的行
grep -E "ro\{2\}t" /etc/passwd

# 匹配包含 "r"与"t"之间有2个或2个以上"o" 的行,比如:root,rooot,roooot,……
grep -E "ro\{2,\}t" /etc/passwd

# 匹配包含 "r"与"t"之间有2个或3个"o" 的行,比如:root,rooot
grep -E "ro\{2,3\}t" /etc/passwd

# 将文档中所以 Love and Hate 的变成 Hate and Love
sed -r "s/(Love) and (Hate)/\2 and \1/" re.txt # \1表示引用第一个()的内容,\2表示第二个()的内容,这里()不需要转义,加转义反而会报错

正则练习

说出如下匹配规则

1. ^love
2. love$
3. l.ve
4. lo*ve
5. [lL]ove
6. love[a-z]
7. love[^a-zA-Z0-9]
8. .*
9. ^$
10. ^[A-Z]..$
11. ^[A-Z][a-z]*3[0-5]
12. [a-z]*\.
13. ^ *[A-Z][a-z][a-z]$
14. ^[A-Za-z]*[^,][A-Za-z]*$
15. \<fourth\>
16. \<f.*th\>
17. 5{2}2{3}\.
18. ^[ \t]*$
19. ^#
20. ^[ \t]*#

答案

1. 匹配行首为love开头 的行
2. 匹配行尾以love结尾的 的行
3. 匹配"l"与"ve"间有任何一个字符 的行,比如:love,l0ve,lave,……
4. 匹配"l"与"ve"间有0个或多个o 的行,比如:lve,love,loove,……
5. 匹配有1ove或Love 的行
6. 匹配love + 小写字母 的行
7. 匹配love + 非字母和数字的字符 的行
8. 匹配所有内容
9. 匹配空行(注意:有空格或tab的行匹配不上)
10. 匹配首字母大写 + 任意两位为结尾 的行
11. 匹配首字母大写 + 0或多个小写字母 + 3 + 0到5任意一个整数
12. 匹配0或多个小写字母 + . # 注意"."被转义了
13. 匹配首字符为0或多个空格 + 大写字母 + 小写字母 + 小写字母结尾
14. 匹配首字符为0或多个字母 + 逗号外的字符 + 0或多个字母结尾
15. 匹配单词词首和词尾都是fourth
16. 匹配f开头,结尾是th,中间是任意字符的单词
17. 匹配2个5 + 3个2 + .
18. 匹配行首开头结尾都是0或多个空格或TAB键(制表符) 的行
19. 匹配以#号开头 的行 (寻找注释行)
20. 匹配开头是0或多个空格或TAB键(制表符) + # (也是寻找注释行)

来分析一下如下常用的正则

  1. 国内手机号:^1[3-9]\d{9}$
  2. 邮箱:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
  3. 中文:[\u4e00-\u9fa5]
  4. 中文(含标点符号):[\u4e00-\u9fa5\ufe30-\uffa0]
  5. 匹配邮编:[1-9]\d{5}(?!\d)
  6. 匹配IP:\d+\.\d+\.\d+\.\d+
  7. 匹配HTML标签:<(\S*?)[^>]*>.*?</\1>|<.*? />
  8. 匹配每行前后空字符:(^\s*)|(\s*$)
  9. 匹配格式化数字:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
  10. 匹配复杂密码(大小写+数字+特殊字符+长度至少8位):^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]).{8,}$

更多shell正则教程