写这篇文章的原因是,我在VScode上熟练使用正则表达式,但是当我在 grep
、find
、vim
等工具中使用正则表达式时,往往会遇到各种问题。即便在 VSCode 上正常工作的正则表达式,在这些工具中却突然失效,甚至报错,这让我感到非常困惑。直到今天,我才意识到这些问题是因为 grep
等工具的正则表达式实现与 VSCode 所用的正则表达式标准不同导致的。
正则表达式分类
在Linux中,正则表达式主要有三种类型:
- 基本正则表达式(Basic Regular Expression,BRE)
- 扩展正则表达式(Extended Eegular Expression,ERE)
- Perl兼容正则表达式(Perl-Compatible Regular Expression,PCRE)
BRE
和ERE
可以进一步分为POSIX BRE/ERE
和GNU BRE/ERE
。
在详细了解每种类型之前,先了解它们的起源和为什么存在多种正则表达式类型。
最初,BRE定义了一系列元字符,例如星号和方括号,以在模式匹配的上下文中具有特殊含义。后来,ERE扩展了这个想法,加入了更多的元字符,以允许更灵活的匹配模式。这两种类型随后在POSIX标准中进行了规范化。
后来,GNU项目在POSIX BRE和ERE的基础上进行了扩展,以进一步增强其命令行工具,如grep、gawk和sed。由于这些扩展不符合POSIX的定义,因此它们被称为GNU BRE和GNU ERE。
最后,PCRE是正则表达式的后续版本,包含了更高级的功能。PCRE是PHP、Java和JavaScript等语言中最常采用的标准,尽管其实现通常不完全遵循规范,并且存在一些小的变动。
从集合的角度来看,POSIX BRE的功能是POSIX ERE的一个子集,而POSIX ERE又是GNU BRE和ERE的一个子集。在所有类型中,PCRE是所有类型的超集。
POSIX BRE
和 ERE
POSIX BRE
被认为是仍在使用的最古老的正则表达式类型。因此,它具有最基本的模式匹配运算符集。具体来说,POSIX BRE
支持.
、^
、$
、*
和{}
)量词,以及字符集表达式(bracket expression)。
而POSIX ERE
对这些运算符进行了扩展,POSIX ERE
还支持通过管道符号(|
)进行选择。POSIX ERE
还定义了加号(+
)和问号(?
)量词,以支持匹配 1 个或多个以及 0 个或 1 个的模式。
BRE
的一个显著特点是反斜杠(\
)必须位于某些元字符之前,以使它们具有特殊含义。具体而言,像 {
、}
、(
和 )
这样的元字符在没有反斜杠的情况下失去了它们的特殊含义。这与 ERE 不同,在 ERE 中,前置反斜杠使这些元字符失去特殊含义。
GNU BRE
和 ERE
GNU BRE
和 GNU ERE
是在 POSIX BRE
和 ERE
之上的扩展。这意味着 GNU BRE
和 ERE
能够处理 POSIX
等效的所有功能及更多功能。此外,GNU BRE
在功能上与 GNU ERE
相同,因为它们定义了一组相同的功能,不同于 GNU 的。
它们唯一的区别是 GNU BRE
需要使用反斜杠字符来赋予与 GNU BRE
相同的一组元字符特殊含义。例如在GNU BRE
中使用+
运算符需要添加\+
才会生效。
简单来说就是:在ERE
和PCRE
中,可以直接使用*, +, ?, (, ), {, }, |, .
这些字符,但在BRE
中,需要在前面加上转义符\*, \+, \?, \(, \), \{, \}, |, \.
单词边界
除了所有 POSIX 功能,GNU BRE 和 ERE 还包括对单词边界的支持,使用 \b
、\B
、\<
和 \>
操作符。具体来说,\b
操作符指定单词的开始或结束,而 \B
是 \b
的取反版本。例如,假设我们有一个包含2行的 word-boundary.txt
文件:
$ cat word-boundary.txt
brownpancakes
brown pancakes
我们可以通过匹配带有单词边界的 brown
来选择第二行:
$ cat word-boundary.txt | grep -E "brown\b"
brown pancakes
另一方面,我们可以使用 \B
来选择不带单词边界的第一行:
$ cat word-boundary.txt | grep -E "brown\B"
brownpancakes
此外,还有两个操作符允许我们准确匹配单词的开始或结束。具体来说,\<
操作符专门匹配单词的开始,\>
操作符匹配单词的结束。
简写
此外,GNU 扩展还支持简写类,例如 \w
和 \s
,提供了常用字符类的简洁语法。具体来说,我们可以使用 \w
语法来匹配任何字母数字值,它等同于 POSIX 的方括号语法 [a-zA-Z0-9_]
。
类似地,\s
语法是匹配空格、制表符、回车符、换行符或换页符的简写,等同于 [ \t\r\n\f]
。
PCRE
PCRE 是在本文章中提到的不同正则表达式标准中功能最完整的标准。事实上,现在流行的正则表达式引擎通常实现了 PCRE 标准的变体,以提供更完整的正则表达式体验。让我们看看 PCRE 的一些附加功能。
先行断言和后行断言
断言操作符是一个强大的正则表达式功能。它有两种变体:先行断言和后行断言语法。对于先行断言,有正向和负向版本,以允许取反表达式。例如,我们可以写一个正向先行断言表达式来匹配文本 brown
后跟 fox
,而不考虑 fox
作为匹配项:
brown(?=fox)
另一方面,为了匹配 brown
后面没有 fish
这个单词的文本,我们可以使用负向先行断言表达式:
brown(?!fish)
要向后匹配,我们使用后行断言。为了使表达式匹配前面有 brown
的 fox
文本,我们可以使用正向后行断言语法:
(?<=brown)fox
类似地,我们可以使用负向后行断言匹配文本 fish
,紧跟在单词 brown
之后:
(?<!brown)fish
没有先行和后行断言语法,我们只能将整个 brownfish
作为匹配项,而不仅仅是单词 brown
。
懒惰修饰
默认情况下,所有量词(如 ?
、*
和 {n,}
)都是贪婪的。换句话说,它们会消耗尽可能多的匹配项。考虑一个字符串:
goooooo
像 go+
、go*
或 go{1,}
这样的表达式在匹配目标时是贪婪的。具体来说,这些表达式会匹配整个 “goooooo” 文本,尽管它们只需要一个 “o” 在 “g” 字符之后。通过应用懒惰修饰符,我们可以使这些表达式消耗它们所需的最少标记。具体来说,这些表达式的“懒惰”等价形式是 go+?
、go*?
和 go{1,}?
。
总结
Vscode默认使用PCRE
vim默认使用BRE,搜索框输入\v
后接正则表达式,启用ERE
grep默认使用BRE,grep -E
启用ERE,grep -P
启用PCRE
find默认使用BRE,find -regex
启用ERE
用法参考:终于明白vim 和 grep 中 的正则表达式的用法, vim 正则表达式 和grep基本正则表达式 几乎一样
对于不同的语法:只要知道大概就可以了,没有必要深入纠结,因为在不同的系统和不同的软件版本下,具体是使用那种正则表达式规则,谁也说不清楚,只有通过查看源代码才能得知,例如grep也不是严格的BRE语法,grep无论是否添加-E
都能支持\w
运算符。