Linux 中的正则表达式

警告
本文最后更新于 2023-10-22,文中内容可能已过时。

Linux 中的正则表达式

glob 风格通配符跟正则同属于 POSIX 字符集(比如[[:upper:]]等字符集合都是一样),只是侧重点不一样。

glob 风格通配符

所谓的 glob 风格通配符,即使用特定的字符(被称为元字符),实现快速匹配字符串的目的。这个目的与正则表达式基本相同,但是 glob 风格通配符相比正则表达式要简单很多,目前在 Linux Shell 中比较常见,另外在 redis 数据库中的订阅、发布功能中也被使用。glob 风格通配符经常用于文件名路径名的匹配

glob 风格通配符相关的元字符常用的包括,* , ? , [] , [^] , {a,b},!

  • *:匹配零个或者多个(任意)字符

  • ?:匹配一个字符

  • []:匹配指定集合中的任意单个字符,比如[abc]表示匹配单个字符 a 或者 b 或者 c,注意,不能使用[A-Z]等范围表达式,只能使用[[:upper:]]这种表示范围。

  • {a,b}:匹配 a 或者 b,a 与 b 也是通配符,可以由其他通配符组成

  • !:表示非,比如!1.txt表示匹配1.txt以外的文件

  • [0-9]:匹配单个数字

  • [^]:匹配指定集合之外的其他任意单个字符,比如[^abc]表示匹配除了 a、b、c 以外的其他任意字符

  • [[:upper:]]:匹配任意单个大写字母

  • [[:lower:]]:匹配任意单个小写字母

  • [[:digit:]]:匹配任意单个数字,等价于[0-9]

  • [[:alpha:]]:匹配任意单个字母,包括大写字母与小写字母

  • [[:alnum:]]:匹配任意单个字母与数字

  • [[:space:]]:匹配单个空白字符

  • [[:punctl:]]:匹配单个标点符号

可以看到元字符数量明显比正则表达式少,而且有的符号意义还不一样,比如{}

那什么时候使用 glob 风格通配符,什么时候使用正则表达式呢?

只要是明确指定参数为正则表达式的地方,都是正则表达式,比如 find -regex、grep、egrep,其他地方都是 glob 风格通配符。

glob 风格通配符在表示路径和文件名的时候不能也没法用引号包起来,但是正则表达式一定要用引号包起来

glob 其实是 Bash 的模式拓展,跟正则表达式有着本质的不同,具体请看《Bash 的模式拓展》

正则表达式

在 Linux 中,常用的正则表达式有:

POSIX 基本正则表达式(BRE)引擎,包含 ^ $ . [] *

POSIX 扩展正则表达式(ERE)引擎,包含 BRE 之外,还额外包含 () {} ? + |

然而(这也是有趣的地方),在 BRE 中,字符 “+” ,"(",")","{",和 “}“用反斜杠转义后,被看作是元字符,相反在 ERE 中,在任意元字符之前加上反斜杠会导致其被看作是一个文本字符。很奇怪。

在书写正则表达式的时候,为了防止 shell 自己解析要匹配的字符串,最好用引号将文件名模式引起来,单引号双引号都可以。glob 风格通配符可以不用引号包起来,但是正则表达式一定要用引号包起来。这一点至关重要, 这样可以阻止 shell 试图展开它们

如果想要使用这些特殊字符作为普通的文本字符,就需要转义(escape)它,即是在该字符前添加一个特殊字符,向正则表达式引擎说明:它应该将下一个字符解释为普通文本字符。实现该功能的特殊字符是:\反斜杠字符,注意,\本身也需要转义。echo "\\" 输出 \

Linux 中的正则和 Java JavaScript 中的正则中的符号代表的意义基本一样。只有无法识别 \d \w这样的符号,想要表示数字,就只能用[0-9] 表示字母:[a-z]


^ $ 锚点

插入字符(^)尖角号定义从数据流中文本行开头开始的模式,结合^#,由于#在 Linux 代表注释

美元符号$特殊字符定义结尾定位,在文本模式之后添加这个特殊字符表示数据行必须以此文本模式结束。^$ 表示空行

. 任意字符

点特殊字符用于匹配除换行符之外的任意单个字符,但点字符必须匹配一个字符;如果在圆点位置没有字符,那么模式匹配失败。

[abcd123][0-9][a-z][A-Za-z0-9][^0-9]等中括号表达式和字符类

字符类可以定义一个字符集合来匹配文本模式中的某一位置。为了定义字符类,需要使用方括号。应该将要包括在该类中的所有字符用方括号括起来,然后模式中使用整个字符集合,就像任意的其他通配符一样。其中插入字符(^)和连字符(-)这两个元字符有着特殊意义,插入字符用来表示否定,连字符用来表示一个字符区域。

否定,用于查找不在该字符类中的字符,只需在字符类范围的开头添加插入字符^才会有否定的作用,否则,它会失去它的特殊含义,变成字符集中的一个普通文本字符。.即使使用否定,字符类仍必须匹配一个字符

*

在某个字符之后加一个星号表示该字符在匹配模式的文本中不出现或出现多次

?

问号表示前面的字符可以不出现或者出现一次,简而言之就是使前面的元素可有可无。不匹配重复出现的字符。

+

加号表示前面的字符可以出现一次或者多次,但必须至少出现一次,该字符若是不存在,则模式不匹配。

{}

使用大括号指定对可重复的正则表达式的限制,通常称为间隔。

{n,m} //匹配前面的元素,如果它至少出现了 n 次,但是不多于 m 次。

{n,} //匹配前面的元素,如果它出现了 n 次或多于 n 次。

{,m} //匹配前面的元素,如果它出现的次数不多于 m 次。

{n} //匹配前面的元素,如果它确切地出现了 n 次。

注意,glob 风格通配符中的{}和正则表达式中的{}意思差别很大*的意思也有差别。

特殊字符类

用于匹配特定类型的字符。

[[:blank:]] 空格(space)与定位(tab)字符

[[:cntrl:]] 控制字符

[[:graph:]] 非空格(nonspace)字符

[[:space:]] 所有空白字符

[[:print:]] 可显示的字符

[[:xdigit:]] 十六进制数字

[[:punct:]] 所有标点符号

[[:lower:]] 小写字母

[[:upper:]] 大写字母

[[:alpha:]] 大小写字母

[[:digit:]] 数字

[[:alnum:]] 数字和大小写字母

跟 glob 风格通配符一样


在 Linux 系统中,/usr/share/dict/linux.words,是一个字典文件,我们可以在这里进行正则练习:

1
grep -i '^..j.r$' /usr/share/dict/words

{}很奇怪,尤其是 find 中的{},grep 中的{}需要加\转义,egrep 中的可以直接使用(grep 使用的是 BRE,egrep 使用的是 ERE),但是find -regex无法使用{},{}一直都只能被当成普通文本字符进行匹配,本来还以为find -regex使用的是 BRE,但是 find 可以使用+ 彻底晕了。TODO

0%