第 1 章 正则表达式简介
在你打开这本书的时候,很可能已经热切地期望,要在代码中插入本书中找到的那些包含诸多括号和问号的古怪字符串了。如果你已经准备好要“即查即用”,我们非常欢迎,第4~9章中会列出并讲解了各种实用的正则表达式。
但是如果阅读本书的前几章,你将为未来节省大量的时间。例如,本章会向读者介绍许多工具—其中一些工具是本书作者之一的Jan所开发的,这些工具可以帮你事先测试和调试正则表达式,而不用等到把它们塞到代码中之后再处理,那时候查找错误就非常困难了。而且开始这几章还会展示使用正则表达式的不同特性和选项,帮助你轻松应对遇到的问题,并帮助你理解正则表达式,从而提高它们的性能,以及学习不同语言—甚至是你最喜欢的编程语言的不同版本之间—在处理正则表达式的时候出现的细微差别。
因此,我们在这些背景知识上花费了大量的精力,相信读者在开始动手之前会阅读这些内容,或是在使用正则表达式时遇到挫折,而想要巩固你的理解的时候,会回头来阅读它们。
1.1 正则表达式的定义
在本书的上下文中,正则表达式(regular expression)是一种可以在许多现代应用程序和编程语言中使用的特殊形式的文本模式。它们可以用来验证输入是否符合给定的文本模式;在一大段文本中查找匹配该模式的文本;用其他文本来替换匹配该模式的文本或者重新组织匹配文本的片段;把一块文本切分成一系列更小的文本,当然如果使用不当也可能搬起石头砸自己的脚。本书会帮助你确切理解正在做的事情,从而避免可能会造成的灾难性后果。
学会了使用正则表达式的技巧,就可以简化许多编程和文本处理的任务,并且让许多没有正则表达式则根本无法实现的任务成为可能。从一个文档中提取所有的电子邮件地址,至少需要几十行,甚至是几百行过程式代码—这些代码编写起来费事,维护起来也麻烦。但是,如果采用了合适的正则表达式,如在实例4.1中所给的那样,就只需要很少的几行甚至只要一行代码就可以了。
术语“正则表达式”的历史
术语“正则表达式”来源于数学与计算机科学理论,它用来反映被称为“正则性”的数学表达式特点。这样一个表达式可以通过一个确定性有限自动机(DFA)用软件来实现。一个DFA是一个不使用回溯的有限状态机。
最早版本的grep工具所使用的文本模式是数学意义上的正则表达式。尽管名字看起来是一样的,然而如今流行的Perl风格的正则表达式已经完全不是数学意义上的正则表达式了。它们是采用非确定性的有限自动机(NFA)来实现的。你稍后就会学到和回溯有关的所有内容。关于这条说明,实干的程序员需要记住的所有内容就是:象牙塔里的一些计算机科学家,很不喜欢自己精心定义的术语被套用到现实世界中更为有用的技术。
但是,如果你试图用一个正则表达式来做太多的事情,或者是在根本不适合的情形中非要使用正则表达式,就会明白为什么会存在如下的说法1FF:
有些人每遇到一个问题,就会想“我知道怎么做,用正则表达式就可以了。”于是他们就有两个(而不是一个)问题需要解决了。
这些人所遇到的新问题指的就是他们并不会去阅读用户手册,也就是现在你手里的这本书。所以请继续读下去。正则表达式是一个强大的工具。如果你的工作涉及在计算机上编辑或是提取文本,牢固地掌握正则表达式就会为你少度过很多个不眠之夜。
1.1.1 众多正则表达式流派
上一小节的标题确实表述得不那么确切,我们并没有定义正则表达式到底是什么。我们也不可能给出定义。对于哪些文本模式是正则表达式,而哪些不是,并不存在正式的标准来给出严格精确的定义。可以想象得到,每种编程语言的设计人员,以及每款文本处理程序的开发人员,对于正则表达式应该是什么样子,都会有自己不同的想法。因此,我们就不得不面对这样一大堆不同的正则表达式流派。
幸运的是,绝大多数设计人员与开发人员都比较懒惰。如果可以照搬别人已经做好的工作,为什么非要自己创建一些全新的东西呢?正因为此,所有现代的正则表达式流派,包含本书要讨论的这些流派,其历史都可以追溯到Perl编程语言。我们把这些流派都称作Perl风格的正则表达式。它们的正则表达式语法都非常相似,而且在大多数情况下是相互兼容的,但也不是完全如此。
作家也都很懒。在本书中,我们通常会使用regex或regexp来指代一个单个的正则表达式,而使用regexes来指代其复数形式2。
正则流派并不是和编程语言一一对应的。脚本语言倾向于拥有它们自己内置正则表达式流派,其他语言则会依赖于函数库来提供正则表达式支持。有些函数库是对多种语言可用的,而某些特定的语言则会选用一些不同的函数库。
本章要讲解的内容只与正则表达式的流派有关,因此会彻底忽略任何与编程有关的考虑事项。从第3章开始,我们会给出一些代码片段,所以你可以先跳到第3章的第一节,以了解你将会使用哪些流派。但是现在请先忽略所有与编程相关的内容。下面一小节中列出的工具将会通过“动手学习”的方式,让你更方便地探索正则表达式的语法。
1.1.2 本书涵盖的正则流派
在本书中,我们选择了如今最为流行的正则流派。这些都是Perl风格的正则流派。有些流派会比其他流派多一些特性。但是如果两种流派拥有同一个特性的话,那么它们通常都会使用相同的语法。当然也会有例外,当我们遇到这些烦人的不一致情况时,在书中会加以提示。
所有这些正则流派都属于目前正在活跃开发中的编程语言和函数库的一部分。下面的流派列表会告诉你本书所用到的是哪些版本。在本书后面,如果所讲解的正则表达式在所有流派中效果都一样,那么我们在提到该流派时就不会区分其版本。绝大多数场合都是这种情况。除了会影响到一些边界情况的bug修复之外,正则表达式流派一般都不会改变,唯一例外是添加新的特性,原来认为是错误的语法,现在会被赋予新的含义。
.NET
微软公司的.NET框架通过System.Text.RegularExpressions包,提供了一个功能全面的Perl风格正则流派。本书涵盖了.NET 1.0~4.0版。而严格来讲,.NET正则流派只存在两个版本:1.0和2.0版。在 .NET 1.1、3.0和3.5版本中,并没有对正则类进行任何修改。.NET 4.0的正则类有一些新方法,但没有改变正则语法。
任何.NET编程语言,包括C#、VB.NET、Delphi for .NET,甚至包括COBOL.NET,都可以完整使用.NET正则流派。如果一个使用.NET开发的程序提供了正则表达式支持,即使它号称使用的是“Perl正则表达式”,你也可以非常确定它使用的就是.NET正则流派。很长时间里,令人大跌眼镜的一个例外则是Visual Studio(VS)自身。直到Visual Studio 2010,VS集成开发环境(IDE)中依然使用的是它从一开始就在用的一个老版本的正则流派,而这个实现根本就不是Perl风格的。本书写作时正处于测试版的Visual Studio 113FF,最终也在IDE中使用了.NET正则流派。
Java
Java 4是通过java.util.regex包提供内置正则表达式支持的第一个Java版本。这个包很快就超越了各种第三方Java正则库。除了它是标准的和内置的之外,它还支持了功能完整的Perl风格正则流派,其卓越的性能甚至可以媲美使用C语言编写的正则程序。本书会讲解Java 4、5、6和Java 7中的java.util.regex包。如果你在过去几年中用过Java语言开发的软件的话,那么其支持的正则表达式很可能是Java流派。
JavaScript
在本书中,我们使用JavaScript这个术语指代在ECMA-262标准的第3版和第5版中定义的正则表达式流派。这个标准定义了ECMAScript编程语言,而这个语言更广为人知的是它在不同网页浏览器中的JavaScript与JScript实现。Internet Explorer(自5.5版)、Firefox、Opera与Safari都实现了ECMA-262的第3版或第5版。尽管在正则表达式功能方面,JavaScript 3与JavaScript 5的区别很小。然而,所有的浏览器都拥有各种与标准不同的边界情形bug。我们会在必要的地方指出这些问题。
如果一个网站允许使用正则表达式进行查找或者过滤,并且不用等待网站服务器的响应,那么它使用的就是JavaScript正则流派,这是唯一的跨浏览器客户端正则流派。即使是微软的VBScript与Adobe公司的ActionScript 3使用的也是它,不过ActionScript 3添加了一些额外特性。
XRegExp
XRegExp是Steven Levithan开发的开源JavaScript库,可以从http://xregexp.com
下载它。XRegExp扩展了JavaScript的正则表达式语法,并且消除了一些Web浏览器间的不一致。本书中的实例在用到标准JavaScript不支持的正则表达式特性时,会附加使用XRegExp的解决方案。如果一个解决方案的正则流派中列出了XRegExp,就意味着这个解决方案在JavaScript代码中使用XRegExp库时可以正常工作,而不使用XRegExp的标准JavaScript代码无法正常工作。如果一个解决方案的正则流派中列出了JavaScript,则意味着无论JavaScript代码中是否使用XRegExp,都可以正常工作。
本书涵盖了XRegExp 2.0版。所有实例都假设读者使用的是包含全部XRegExp Unicode特性的xregexp-all.js。
PCRE
PCRE是由Philip Hazel开发的“与Perl兼容的正则表达式” (Perl-Compatible Regular Expressions)的C语言函数库。这个开源代码库可以从http://www.pcre.org
下载。本书涉及的PCRE版本包括第4版到第8版。
虽然PCRE号称是与Perl兼容的,而且与本书中的其他流派相比,它也是与Perl兼容性最好的,但是实际上它也只能称为是“Perl风格”的正则流派。有些特性,比如Unicode支持,会与Perl稍微有些不同,并且不能像在Perl中所允许的那样,把Perl代码混合到你的正则表达式之中。
因为它采用了开源许可,并且拥有稳定可靠的实现,所以PCRE被应用到了许多编程语言和程序中。它被内置到PHP中,并且被包装到了许多个Delphi的组件中。如果一个应用程序号称它支持“与Perl兼容”的正则表达式,而却没有具体列出它实际使用的正则流派,那么就很可能是PCRE。
Perl
Perl对于正则表达式的内置支持是正则表达式今天得以流行的主要原因。本书会涉及Perl 5.6、5.8、5.10、5.12和5.14版。上述每个版本都为Perl正则表达式语法添加了新的特性。当本书中标明某正则式在特定Perl版本中工作时,那该正则式可在该特定版本及本书所涉及的所有更新版本中正常工作。
许多应用程序和正则库都号称它们使用的是Perl或者与Perl兼容的正则表达式,而事实上它们仅仅是使用了Perl风格的正则表达式。它们使用与Perl相似的正则语法,但是所支持正则特性集并不重叠。最有可能的情形是,它们使用的是这一特性集中的正则表达式流派之一,而这些流派都是属于Perl风格的。
Python
Python通过它的re模块来支持正则表达式。本书会讲到Python 2.4至3.2版。Python 2.4、2.5、2.6和2.7版间的区别非常小。Python 3.0增强了Python处理正则表达式中Unicode的能力。Python 3.1和3.2则没有与正则相关的改进。
Ruby
与Perl语言类似,Ruby的正则表达式支持是Ruby语言自身的一部分。本书会涉及Ruby 1.8和Ruby 1.9。Ruby 1.8的默认编译会使用由Ruby源代码直接提供的正则表达式流派。而Ruby 1.9的默认编译则会使用Oniguruma正则表达式库。Ruby 1.8也可以编译使用Oniguruma,而Ruby 1.9也可以编译使用旧版本的Ruby正则流派。在本书中,我们会把原生Ruby流派称为Ruby 1.8,而把Oniguruma流派称为Ruby 1.9。
如果想测试一下你的站点使用的是哪个Ruby正则流派,可以尝试用一下正则表达式‹a++›。Ruby 1.8会说这个正则表达式是非法的,因为它并不支持占有量词(possessive quantifier),而它在Ruby 1.9中则会匹配一个或者多个字符a组成的字符串。
Oniguruma库设计为与Ruby 1.8向后兼容,它只是在其上添加了新的功能,而不会破坏已有的正则表达式。该实现甚至原样保留了大家认为应该修改的功能,例如:它依然使用‹ (?m) ›表示“点号匹配换行符”,即使其他正则表达式流派使用的都是‹ (?s) ›。