上一章,我们通过Word中的“使用通配符”模式,粗略见识了正则表达式的使用方法。然而通配符并不等于正则表达式,遇到复杂的情况,通配符就力不从心了。所以从本章开始,我们来看“正宗”的正则表达式。

安装Regular Expression Tester

“工欲善其事,必先利其器”,学习正则表达式也是如此。尽管正则表达式的思想和规则是基本确定的,应用起来却有许多讲究(比如,在Java、C++、Python等不同的编程语言中,同一个表达式的具体写法是不同的,在Word、Excel等软件中也是这样)。所以,学习正则表达式最好采用“中立而规范”的工具——这有点像学习摄影,开始应该学习的是构图、用光,而不是尼康、佳能或索尼相机的特性。

本书中,我们采用Firefox的插件Regular Expression Tester(意思是“正则表达式测试工具”)来学习和讲解正则表达式,选择它的好处在于:不需要搭建编程语言环境(许多时候我们并不需要在编程语言中应用正则表达式);在Windows/Unix/Mac上都可以使用;并且支持大多数通用的正则表达式功能。如果你没有接触过它,也不用担心,下面我们介绍它的安装和使用。

Regular Expression Tester是Firefox(火狐)浏览器的一个插件,因此,如果你没有安装Firefox浏览器,在使用这个插件之前,必须首先安装Firefox浏览器:

进入网址http://www.mozillaonline.com/ ,选择自己需要的版本,点击下载,之后运行下载的程序,完成安装。

图2-1,下载Firefox浏览器

然后,启动Firefox浏览器,选择工具—扩展[xdx1] (在某些版本的Firefox中,这里也可能是“附加组件”),在“获取附加组件”中输入“regular expression tester”,即可搜索到该插件,请点击右侧的“添加至Firefox”按钮,稍后Firefox会提醒尚未验证该插件的作者,没有关系,请选择“立即安装”。安装完毕重启Firefox就可以使用“regular expression tester”插件了。

图2-2,安装Regular Expression Tester

使用Regular Expression Tester

在Firefox的菜单栏选择“工具——扩展”,下拉菜单中选择Regular Expression Tester,就可以看到Regular Expression Tester的主界面。 我们可以看到,这个工具分为三大块,最上面的Regular Expression文本框用于输入正则表达式,中间的Search Text文本框用于输入待匹配的文本,下面的Result则显示匹配(还包括替换,将来我们会看到)的结果,左下角显示正则表达式操作进行的时间,右下角显示匹配发生的次数。

图2-3,Regular Expression Tester的界面

在Regular Expression和Search Text之间,还有几个选项:Case sensitive、Global、Multiline,它们对应到非常实用的功能,后面我们会详细介绍它们的意义和用途,但现在请只勾选Global选项。


图2-4,现在只需要勾选Global

先测试上一章中Word中查找手机号码的例子,把手机号输入Search Text文本框中,每行一个号码,然后在Regular Expression文本框中输入表达式『1310000[0123456789][0123456789][0123456789][0123456789].doc』 ,Result中迅速出现了结果(以黄色高亮标明),表达式匹配成功。

图2-5,『1310000[0123456789][0123456789][0123456789][0123456789].doc』的匹配

如果你仔细观察,会发现Result中每一行的末尾都有一个特殊的符号 ——没错,正则表达式处理的是字符,而每一行末尾的回车/换行符,虽然“看不见”,也是一个字符,为了展示文本的“真实面貌”,Regular Expression Tester用这个符号标识。或许你现在觉得有些别扭,但是请相信我,下面你将会发现这样的好处。

*和?的新含义

接下来,我们测试上一章中的另一个表达式『1310000????.doc』,你一定记得,在勾选了“使用通配符”之后,它就能“大致”匹配1310000号段的手机号码(虽然不一定准确)。现在我们用Regular Expression Tester重复验证一次。

图2-6,『1310000????.doc』匹配出错

出错了!匹配结果栏显示“The entered string is not a regular expression(输入的字符串不是正则表达式)”!问号作为通配符是可以匹配一个任意字符的,那么问题究竟出在哪里呢?

其实,我们已经提到过原因:通配符不等于正则表达式!通配符中*表示“任意字符串”,?表示任意字符,但这只是通配符中的规定,不是正则表达式中的规定!

正则表达式中的*和?

在通配符的世界里,〖*〗表示“任意字符串” ,〖?〗表示“任意字符”(这里的“任意长度字符串”和“单个任意字符”都是为了保持与之前一致,你看是否有全部修改的必要) ,那么,正则表达式中如何表示这两种意义呢?要弄清这个问题,我们先看看正则表达式中的『*』和『?』表示什么意义。 『*』和『?』在正则表达式中都叫做“量词”(英文名叫quantifier),它用来“度量”字符串的长度,也就是字符能够出现的次数,其中『*』表示字符出现次数可以从0到无穷多(也就是“任意长度”了),而『?』表示字符出现0次或者1次。所以,正则表达式『6*』能匹配的,就是空字符串””,或者是”6”、”66”、”66666”……这样的字符串,而正则表达式『6?』能匹配的,只能是空字符串””,或者是单个字符构成的字符串 6

图2-7,『6*』的匹配

注意:regular expression tester”目前无法显示空字符串,所以无法演示匹配空字符串的情况;本例中输入”666666”,你可以自己尝试,无论字符串中包含多少个6,都能匹配。

图2-8,『6?』只能匹配字符串”6”,而不能匹配”66”,”666”

请注意,量词只关心字符出现的次数,与字符没有任何关系:『7*』能匹配空字符串””,”7”,”77”,”777”…,『8*』能匹配空字符串””,”8”,”88”,”888”…。要模拟通配符*,还必须规定每个字符可以是“任意字符”,所以需要有办法表示“任意字符”。好在正则表达式中存在一个特殊符号,也就是普通的点号(英文句号)『.』,它表示“任意字符”,把“任意字符”和“任意长度”组合起来,就得到了“任意字符串”,也就是通配符*的意义了(这里能否麻烦你做个图,我希望是类似一个直角坐标系的,一个原点,两个带单方向箭头的轴,纵向标明“任意字符”,横向标明“任意长度”,图的名称就是“任意字符串”)。

图2-9,『.*』的匹配

现在你多半已经想到了,通配符*表示“任意字符串”,对应的正则表达式是『.*』,那么通配符?表示“任意字符”,恰好就对应了正则表达式中的『.』。因此,在表达式『1310000[0123456789][0123456789][0123456789][0123456789].doc』中,点号『.』其实只是恰好匹配了字符串中的”.”而已,如果此处出现其它字符,也可以匹配。

图2-10,表达式中的『.』“恰好”匹配了【.】

现在我们已经知道了,通配符*和?的功能,在正则表达式中要如何实现。

元字符和转义

前面我们提到了,通配符*、?具有特殊含义,所以不能出现在文件名中。同样具有特殊含义的正则表达式字符『.』、『*』和『?』,是否应该禁止出现在正则表达式要处理的普通文本中?这显然不可能——这几个符号都是很常见的,如果使用正则表达式就不容许它们出现,正则表达式所能处理的文本就非常有限。可是,文本中的*、?等等“特殊符号”要如何来准确匹配呢?

图2-11,正则表达式中的『.』

『.』能匹配任意字符,怎么准确匹配字符串中的”.”? 其实,所谓“特殊符号”就是“意义不同于字符本身的字符”:字符『6』不是“特殊字符”,因为它只能表示字符”6”;相反,字符『*』表示“字符出现次数可以从0到无穷多”,点号『.』表示“任意字符”。在正则表达式中,这些具有特殊意义的“特殊字符”有个统一的名字“元字符(meta-character)”——它的意思是“具有特殊意义的字符”,不管你看它顺不顺眼,记住这个名字就好了。 其实,“元字符”本身的匹配非常简单,只有一条规则:转义。如果某个字符在正则表达式中具有特殊的意义(上面我们提到了『.』和『*』,将来还会遇到更复杂的情况),要表示这个字符本身,就在前面添加反斜线 \ 即可。

图2-12,用『\.』匹配点号

元字符经过转义,就失去了特殊意义,变成了普通字符,所以正则表达式『.\*』的意思就是“先匹配一个任意字符,再匹配一个星号*”。


图2-13,用『.\*』的匹配

如果你觉得转义的概念有些麻烦,不妨这样想:转义只是为了避免混淆的一种形式变化,转义序列\*“看起来”像两个字符构成的字符串,然而从概念上来说,它只表示一个普通字符*。我们要记住的是概念,而不是形式

补充内容:编程语言中的转义

编程中的转义比较特别,值得拿出来讲一讲。

在一般的编程语言中,正则表达式都是以字符串(String)构造的。而对于字符串这种数据类型,多数编程语言都规定了一些转义字符序列来表示特殊字符(注意,这里说的是编程语言中的特殊字符,而不是正则表达式中的“元字符”),比如〖\n〗表示换行符,〖\t〗表示制表符。因此,在编程语言中使用正则表达式时,转义是一个麻烦的问题,但是,我们又必须弄清楚这个问题——网络上时常看到有人提问“这个正则表达式里到底需要几个反斜线?”。

转义的问题其实非常简单:在编程语言中,作为字符串出现的正则表达式,必须经过“字符串的转义”,“正则表达式的转义”才能真正生效。

举例来说,我们的正则表达式里需要出现『\*』,也就是“带转义(去掉了特殊意义)的〖*〗字符”。因为正则表达式是以字符串形式表现的,如果直接在字符串里写 \* ,许多编程语言就会报错:因为它只认识 \t 、 \n 之类的转义序列,不认识 \* 。

解决的办法是在 \* 之前添加反斜线 \ ,写成 \\* 。这样,编程语言在处理字符串时,会首先将 \\ “翻译”成 \ ,正则表达式得到的就是 \* 了。

以上说的情况比较简单,可是,如果我们需要在正则表达式中使用反斜线字符『\』呢?这个字符首先必须以转义形式写出,也就是说,在正则表达式中必须写成『\\』;但每个反斜线,在字符串里又需要转义,所以正则表达式中的一个『\』,在字符串里就必须写成【\\\\】!

不过,也有些语言不会报错——它们遇到“未定义”的转义字符,会直接忽略反斜线的转义功能,将它视为普通字符序列,Python,PHP都是如此(注:JS是否如此尚未验证)。

我的建议是,在对元字符转义时,心里一定要明白应该写几个反斜线;同时,推荐你在使用Python和PHP时,不要省略一个反斜线字符。

小结

在本章,我们真正踏入了正则表达式的殿堂。安装好Regular Expression Tester,就搭建好了学习正则表达式的舞台。

我们也知道了,通配符不同于正则表达式,在正则表达式中,*表示字符出现次数可以从零到无穷多,?表示表示字符出现零次或者一次,它们都是量词,属于元字符。通配符*就等于正则表达式『.*』,通配符?就等于正则表达式『.』。

通配符 正则表达式
? .
* .*

最后要记得的是,如果需要取消元字符的特殊意义,必须使用反斜线进行转义,在一些编程语言中,可能需要两个反斜线字符——我们推荐这么做!