作者:kamly,腾讯 CDC 应用开发工程师

前言

正则表达式在我们日常的软件开发过程中被广泛使用,例如编写 Nginx 配置文件、在 Linux 与 macOS 下查找文件,然而不同软件不同操作系统对于正则的应用有着不一样的行为,主要原因是正则表达式演进过程中,出现POSIXPCRE派系之分。

一、历史

先了解一下正则表达式的演进史。

20 世纪 40 年代,两位神经生理学家 Warren McCulloch 和 Walter Pitts,研究出了一种用数学方式来描述神经网络的方法,可以将神经系统中的神经元描述成小而简单的自动控制元。

50 年代,一位叫 Stephen Kleene 的数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了 论文。这篇论文描述了一种叫做 "正则集合(Regular Sets)" 的数学符号,引入了正则表达式的概念。

60 年代,Unix 之父 Ken Thompson 发表了 论文。并且根据这篇论文的算法,将正则引入到编辑器 qed ,以及之后的编辑器 ed 中,然后又移植到了我们熟悉的文本搜索工具 grep 中。

70 年代,由于 grep 支持的功能不多,因此 Alfred Aho 编写了 egrep 程序(其中e表示加强版的意思)。在 grep 、 egrep 发展的同时, awk 、 lex 、 sed 等异军也开始凸起,每个程序所支持的正则表达式都有差别。

80 年代,POSIX(Portable Operating System Interface) 标准公诸于世,它制定了不同的操作系统都需要遵守的一套规则,其中就包括正则表达式的规则。遵循POSIX规则的正则表达式,称为POSIX派系的正则表达式。Unix 系统或类 Unix 系统上的大部分工具,如 grep 、sed 、awk 等都属于POSIX派系。同样在 80 年代,Larry Wall 发布了 Perl 编程语言,其中引入的正则表达式功能是颗耀眼明珠。

90 年代,随着 Perl 语言的发展,它的正则表达式功能越来越强悍。为了把 Perl 语言中正则的功能移植到其他语言中,PCRE(Perl Compatible Regular Expressions)派系的正则表达式也诞生了。现代编程语言如 Python , Ruby , PHP , C / C++ , Java 等正则表达式,大部分都属于PCRE派系。

总的来说,经历 20 世纪 80 至 90 年代洗礼,正则表达式形成了两大派系:POSIXPCRE

二、POSIX 与 PCRE

POSIX派系 与PCRE派系具体有什么不一样?我们应该何时选择哪个派系?

POSIX 派系

POSIX派系是遵循POSIX规则的正则表达式,其中代表软件有:grep ,sed 和 awk 等。

BRE 和 ERE 标准

POSIX派系分为两种标准:

  1. BRE标准(Basic Regular Expression 基本正则表达式)

  2. ERE标准(Extended Regular Expression 扩展正则表达式)

在 GNU 版本下,两者具体差别如下:

是不是很难找到两者的差别点呢?仔细留意一下,第 3,4,5,7 行的内容。我们能发现,如果使用BRE标准,需要对[],(),|符号进行转义。作者看来ERE实际上是BRE的一个扩展标准,开发者使用ERE能书写更简单的正则表达式,不需要对某些字符进行特殊转义。

POSIX 字符组

POSIX派系有自己的字符组,叫POSIX字符组,具体解释如下所示:

篇幅原因,仅提供部分需要关注的对比,具体看【附录-POSIX 字符组详细内容】。

PCRE 派系

现代编程语言大部分都属于PCRE派系,如 Python , PHP 和 Java 等。

PCRE 与 Perl

  • Perl1 提供了正则表达式操作符——是通用脚本语言的首创;

  • Perl2 补充/i量词,能够进行不区分大小写匹配等;

  • Perl3 支持/e量词,能够增强替换运算符的能力;{min,max}区间量词等;

  • Perl5 添加 非捕获的括号,忽略优先的量词,顺序环视功能等。

随着 Perl 每次迭代,新增的特性使正则表达式本身逐渐成为一门强大的编程语言,并为其提供了进一步发展空间,也因为派系的整合,PCRE库横空出世,它是一套兼容 Perl 正则表达式库,全面仿制 Perl 的正则表达式的语法和语义。其他开发人员可以把PCRE库整合到自己的工具和语言中,为使用者提供丰富的正则功能。

特性

  1. 更易用

    相对于POSIX派系的BRE标准,不需要使用 \ 进行转义。

    例如:在多选分支结构直接使用|即可(1|2表达 1 或者 2)

  2. 更简洁

    在兼容POSIX字符组的基础上还支持更简洁的写法。

    例如:\w等价于[[:word:]]\d等价于[[:digit:]]

  3. 更多功能

    例如:Look-around (环顾断言), Non-capturing Group (非捕获组), non-greedy (非贪婪)等。

总结

正因为PCREPOSIX相比,PCRE使用起来更加易用简洁(不需要转义,有更简洁字符组),功能更加丰富(非捕获组,环顾断言,非贪婪)。如果没有特殊原因,应尽可能使用PCRE派系,让正则匹配的结果更符合我们预期。

篇幅原因,仅提供部分需要关注的对比,具体看【附录-PCRE、GNU BRE、GNU ERE 对比】。如果读者对贪婪和非贪婪模式感兴趣,可以了解一下,或许会让你对正则表达式产生新的看法。

三、实战

了解完PCRE派系和POSIX派系后,我们来做个简单的测试。文本内容如下,我们目标是需要匹配其中的数字:

12345
abcde

实验环境为 Linux 与 macOS 下的 grep ,分别使用:

  • 不带参数,为POSIX BRE模式;

  • 带参数 -E,为POSIX ERE模式;

  • 带参数 -P,为PCRE模式( macOS 不支持)。

实验结果如下图:
打开网易新闻 查看精彩图片
实验结论 在 Linux 环境下

通过man grep,可以了解到 Linux 下的 grep 默认是POSIX BRE模式:

-G, --basic-regexp
Interpret PATTERN as a basic regular expression (BRE, see below). This is the default.

加上-E则是POSIX ERE模式:

-E, --extended-regexp
Interpret PATTERN as an extended regular expression (ERE, see below).

加上-P则是PCRE模式:

-P, --perl-regexp
Interpret the pattern as a Perl-compatible regular expression (PCRE). This is experimental and grep -P may warn of unimplemented features.
在 macOS 环境下

从实验结果来看,grep '\d' demo.txt'命令在 Linux 与 macOS 输出是不一样的,这是因为 macOS 自带的 grep 是 BSD 版本,而 Linux 下的 grep 则是 GNU 版本。

macOS 基于 BSD,预置 BSD 工具链,众多命令行工具与 Linux 下 GNU 工具的行为不一致,例如常见的 gzip , find 和 sed ,以及本文重点提及的 grep。

读者如果希望自己的 macOS 电脑能完美运行 GNU/Linux 上的 Shell 脚本,可以使用 homebrew 来逐一替换,例如本文提及的 grep 可以通过brew install grep

总结

正则表达式以及相关生态在发展了数十年的情况下,应用场景已经非常广泛。读者在使用软件工具的时候,应需要了解该工具支持正则表达式何种派系,避免执行脚本迁移不同环境后运行结果不符合预期。

例如:

  1. 确认版本类型(GNU , BSD)。建议统一使用 GNU 中 grep 程序,避免在不同环境下运行结果不符合预期的现状

  2. 确认每个模式下的选项(BRE,ERE,PCRE)。尽可能选择PCRE模式,因为PCRE模式更符合我们的使用习惯。

此外,除了关心正则表达式的标准之外,强烈推荐读者细读,或许能帮助你写出更性能更好的正则表达式,避免因为正则表达式的地狱回溯导致的应用程序的 OOM。

BSD 是加州大学伯克利分校对 Unix 系统进行的扩展与重新发行。目前的 BSD 生态系统围绕三大主要操作系统:

  1. FreeBSD、OpenBSD、NetBSD

  2. DragonFly BSD

  3. 其他发行版

参考资料
  • 《精通正则表达式》第 3 版









招人正则匹配中,欢迎扫码命中

腾讯程序员视频号最新视频