相信大家对于正则表达式都不陌生,在文本处理中或多或少的都会使用到它。但是,我们在使用linux下的文本处理工具如awk、sed等时,正则表达式的语法貌似还不一样,在awk中能正常工作的正则,在sed中总是不起作用,这是为什么呢?
这个问题产生的缘由是因为正则表达式不断演变的结果,为了弄清楚这些工具使用的正则语法的不同,我们有必要去简单了解下正则的演变过程,做到知己知彼。当然这个过程本身也是很精彩的,我这里抛砖引玉,希望对大家正确使用正则表达式有所帮助。
诞生期
正则表示式这一概念最早可以追溯到20世纪40年代的两个神经物理学家 Warren McCulloch 与 Walter Pitts,他们将神经系统中的神经元描述成小而简单的自动控制元; 紧接着,在50年代,数学家1950年代,数学家 Stephen Kleene 利用称之为“正则集合”的数学符号来描述此模型,并且建议使用一个简单的概念来表示, 于是 regular expressions 就正式登上历史舞台了。插播一下,这个 Kleene 可不是凡人,大家都知道图灵是现代人工智能之父, 那图灵的博导是Alonzo Church,提出了 lambda 表达式,而 Church 的老师,就是 Kleene 了。关于 lambda,之前也写过一篇文章, 读者可以参考编程语言的基石。
在接下来的时间里,一直到60年代的这二十年里,正则表示式在理论数学领略得到了长足的发展,Robert Constable 为数学发烧友们写了一篇总结性文章
由于版权问题,我在网上没找到这篇文章,大家有兴趣的可以参考Basics of Automata Theory。
Ken Thompson 在 1968 年发表了 Regular Expression Search Algorithm 论文,紧接着 Thompson 根据这篇论文的算法实现了 qed,
qed 是 Unix 上编辑器 ed 的前身。 ed 所支持的正则表示式并不比 qed 的高级,但是 ed 是第一个在非技术圈广泛传播的工具,
ed 有一个命令可以展示文本中符合给定正则表达式的行,这个命令是 g/Regular Expression/p
,在英文中称为 “Global Regular Expression Print”,
由于这个命令非常实用,所以后来有了 grep、egrep 这两个命令。
成长期
相比 egrep,grep 只支持很少的元符号, *
是支持的(但不能用于分组中),但是 +
|
?
是不支持的;而且,分组时需要加上反斜线转义,
像 \( ...\)
这样才行,由于 grep 的缺陷性日渐明显,AT&T的 Alfred Aho 实在受不了了,于是 egrep 诞生了,
这里的 e 表示 extended,加强版的意思,而且支持了 +
|
?
这三个现在大家都十分熟悉元符号,并且可以在分组中使用 *
,
分组可以直接写成 (...)
,同时支持用 \1,\2...
来引用分组。
在 grep、egrep 发展的同时,awk、lex、sed 等程序也开始发展起来,而且每个程序所支持的正则表达式都或多或少的和其他的不一样,
这应该算是正则表达式发展的混乱期,因为这些程序在不断的发展过程中,有时新增加的功能因为 bug 原因,在后期的版本中取消了该功能。
例如,如果让 grep 支持元符号 +
的话,那么 grep 就不能表示字符 +
了,而且 grep 的老用户会对这很反感。
成熟期
这种混乱度情况一直持续到了 1986 年,这一年 POSIX(Portable OperatingSystem Interface)标准公诸于世, POSIX 制定了不同的操作系统都需要遵守的一套规则,当然,正则表达式也包括其中。
除了 POSIX 标准外,还有一个 Perl 分支,也就是我们现在熟知的 PCRE,随着 Perl 语言的发展,Perl 语言中的正则表达式功能越来越强悍, 为了把 Perl 语言中正则的功能移植到其他语言中,PCRE 就诞生了。现在的编程语言中的正则表达式,大部分都属于PCRE这个分支。
下面重点说说这两个分支。
POSIX
POSIX 把正则表达式分为两种(favor):
- BRE,Basic RegularExpressions
- ERE,Extended Regular Expressions
所有的 POSIX 程序可以选择支持其中的一种。他们俩的区别如下:
从上图可以看出,有三个空白栏,那么是不是就意味这无法使用该功能了呢?答案是否定的,因为我们现在使用的 Linux 发行版, 都是集成 GNU 套件(Gnu's Not Unix)的,GNU 在实现了 POXIS 标准的同时,做了一定的扩展,所以上面空白栏中的功能也能使用。下面一一讲解:
- BRE如何使用
+
?
呢?需要用\+
\?
- BRE如何使用
|
呢?需要用\|
- ERE如何使用
\1
\2
\9
这样的反引用?和BRE一样,就是\1
\2
通过上面总结,可以发现:GNU 中的 ERE 与 BRE 的功能相同,只是语法不同(BRE 需要用 \
进行转义,才能表示特殊含义)。
例如 a{1,2}
,在 ERE 表示的是 a
或 aa
,在BRE中表示的是 a{1,2}
这个字符串。
为了能够在Linux下熟练使用文本处理工具,我们必须知道这些命令支持那种正则表达式。现对常见的命令总结如下:
BRE | ERE |
---|---|
grep、ed、sed、vim | egrep、awk、emacs |
当然,这也不是绝对的,比如 sed 通过 -r
选项就可以使用 ERE 了,具体命令的用法,可以 man 一下。
还值得一提的是, POSIX 还定义了一些 shorthand 来表示常用的字符范围,具体如下:
[:alnum:]
[:alpha:]
[:cntrl:]
[:digit:]
[:graph:]
[:lower:]
[:print:]
[:punct:]
[:space:]
[:upper:]
[:xdigit:]
在使用这些 shorthand 时有一个约束:必须在 []
中使用,也就是说如果像匹配 0-9
的数字,需要这么写 [[:alnum:]]
,取反就是 [^[:alnum:]]
。shorhand 在 BRE 与 EBE 中的用法相同。
如果你对 sed、awk 比较熟悉,你会发现我们平常在变成语言中用的 \d
\w
在它们中不能用,原因很简单,
因为 POSIX 规范根本没有定义这些 shorthand,这些是由下面将要说的 PCRE 定义的。
PCRE标准
Perl语言第一版是由 Larry Wall 发布于 1987 年 12 月,Perl 在发布之初,就因其强大的功能而一票走红, Perl 的定位目标就是“天天要使用的工具”。Perl 比较显诸特征之一是与 sed 与 awk 兼容,这造就了 Perl 成为第一个通用性脚本语言。
随着 Perl 的不断发展,其支持的正则表达式的功能也越来越强大。其中影响较大的是于1994年10月发布的Perl 5,其增加了很多特性,
比如non-capturing parentheses、lazy quantifiers、look-ahead、元符号 \G
等等。
正好这时也是 WWW
兴起的时候,而 Perl 就是为了文本处理而发明的,所以 Perl 基本上成了 web 开发的首选语言。
Perl 语言应用是如此广泛,以至于其他语言开始移植 Perl 的语法,最终Perl compatible(兼容)的 PCRE 诞生了,
这其中包括了Tcl, Python, Microsoft's .NET , Ruby, PHP, C/C++, Java 等等。
PCRE 也定义了一些列 shorthand,常用的有如下这些:
\w
表示[a-zA-Z]
\W
表示[^a-zA-Z]
\s
表示[ \t\r\n\f]
\S
表示[^ \t\r\n\f]
\d
表示[0-9]
\D
表示[^0-9]
\<
表示一个单词的起始\>
表示一个单词的结尾
关于 shorthand 在两个标准的比较,可参考 Wikipedia 上的 Character_classes。