Categories: 在线文档

正则表达式傻瓜书 第二章:元字符

本文由Yurii原创,转载请注明来源: Life Sailor

本文链接 正则表达式傻瓜书 第二章:元字符


上一章,我们通过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,就搭建好了学习正则表达式的舞台。

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

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

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

Yurii

Recent Posts

德国育儿经验:家长需要和儿童谈论”空气动力学“吗?

家长应当和儿童,尤其是低龄儿童谈论“空气动力学”吗? 我的答案曾经是非常肯定的:不应当。不要说儿童,就是成年人也不见得理解这些抽象的概念,与儿童谈论这些名词,只会让人望而生畏。身为父母,我们应当做的是,以孩子能理解的、感兴趣的方式谈论相关的具体问题,但绝对不要提这些大词。 不过世界的奇妙就在于,父母对教育并没有绝对的权威,总是需要根据实际情况来修正自己的观点。在“空气动力学”的问题上,我就吃到了教训。 那是一个下午,家里小朋友在iPad上看完他最喜欢的Blippi(这个节目我之前介绍过,对80后父母来说,Blippi可以理解为“带你见识各种新鲜玩意的董浩叔叔”),忽然抬起头来问我:“爸爸,你知道什么是aerodynamics吗?” “什么?你问我知不知道什么是aerodynamics?”我的下巴都要掉下来了。“空气动力学”这种词还是上中学时,身为军迷的我们在《航空知识》上知道的。再往后英语好一些,能看原版科普视频了,才知道“空气动力学”的原文就是aerodynamics。可是,我家这个还没上小学的家伙,竟然就能真诚地瞪大眼睛,一本正经地问我“知不知道什么是aerodynamics”。 (more…)

3 days ago

忆孟繁超老师:他从来没有给我上过一堂正式的课,但我永远都是他的学生。

我本来是不应该认识孟老师的。 2001年,我在寝室夜谈里第一次听到孟老师的名字。当时有同学说“公共选修课的《法学概论》讲得真好,那个老师叫孟繁超”,开始我不怎么在意,慢慢才发现这么说的人还不少。那个年月网上的资料正丰富,出版管制也不那么严格,刚进大学不久的我正自由自在地看得过瘾,心想“大学里的法学概论讲再好,能讲些什么,还不是教科书上老一套”,所以这种课,不听也罢。 但生活就在这么奇妙。那年冬天,有天中午我吃过饭正准备午睡,忽然有人敲门问“计算机系有位叫余晟的同学在这里吗?” 大中午的谁会来找我?我正好奇这个问题,门一推开就有同学喊“孟老师,孟老师来了”。 那是我第一次见到孟老师,中年人,国字脸,身材高大,打扮很精神,披在身后的深色大衣让我一下子想起电影里的斗篷。他笑眯眯地说“你是余晟?听同学说你搞电脑很厉害,我家的电脑坏了,想请你去看看。” (more…)

3 days ago

“历史照进现实”,这似乎不太现实

中国人大概都对历史有一些特别的偏好。对我们普通人来说,历史首先是文化的象征,一个人“懂历史”,基本等于这个人“有文化”;历史也是民族自豪感的来源,哪怕考古上仍然存在争议,但是“五千年文明”的说法是普通人都耳熟能详的。 不过等我长大之后才发现,这种偏好大概还有更深层次的原因,那就是历史看起来有种道德的意味,因为我们从小就熟悉“以史为鉴”的智慧,也熟悉各种“历史的选择”:每当我们对现实感到失望、困惑的时候,我们经常去历史——而不是先贤的智慧中——中寻找解答。找到曾经发生的类似的故事,就可以预言未来的结局。 于是乎,失望也好、困惑也罢,总归会有光明的未来,历史总会给我们支撑的信念。 我曾经很相信,熟谙历史是种智慧,而且是深层次的智慧。但是看得越多、经历得越多,我就越觉得,这很难称之为“智慧”。 为什么? (more…)

3 days ago

无人出租车,是技术进步的一粒灰,还是普通人头上的一座山?

“无人出租车要来了”。以百度“萝卜快跑”为代表的无人出租车,眼看就要在国内多个城市成规模运营。 熟悉IT的人都知道,IT的独特优势就在于“大规模扩展时边际成本极低”。在软件时代,微软开发的Windows,多卖一份的成本只是多刻录一张光盘而已。在无人驾驶时代,从10辆车到10万辆车的成本,也遵循同样的规律。换句话说,一旦模式“跑通”了,就可以迅速大规模铺开。无人出租车的大规模应用,也是“指日可待”了。 只不过,新技术这一次似乎没有那么激动人心,反而引起了很多争议——无人驾驶出租车大规模推广,会不会影响广大出租车、网约车车主的收入甚至生计?如果是,这样的技术进步,真的是我们所需要、所期待的吗?对于这个问题,不同的人有相差迥异的答案。 按照我的观察,许多人对此是相当乐观的。理由在于,“技术的每一次飞跃发展,虽然有阵痛,最终都创造了更多的新岗位”。既如此,无人出租车短期“看似”抢了许多人的饭碗,但也只是短期的“阵痛”而已。看看历史,纺织机的发明,蒸汽机的改良,汽车的诞生,无不证明了“阵痛说”的正确性。 坦白说,这种观点我是怀疑的。 (more…)

3 days ago

回国感受:松弛一点,愉快一点

因为小朋友放暑假,近期带小朋友回国待了几个礼拜。最深的感受就是标题所说的:松弛一点,愉快一点。 我第一次突出意识到这点,是在上海下飞机乘地铁。当时我们乘的直梯就要关门,远远看见有个年轻小伙子跑过来,我连忙按住开门按钮,并招呼他”别着急,慢慢来“,等他进了轿厢才关门。本来我以为大家起码会打个招呼,露个笑脸,因为我已经习惯如此,但完全出乎我意料的是,他进来之后对我们完全视若不见,自顾自掏出手机,盯着看得入迷。 我继而发现,不管是在电梯里,站台上,还是车厢里,虽然四下里都是广播”请扶好站稳,抓好扶手,不要看手机“,但是似乎人人都盯着自己的手机。年轻人在打手机游戏,年纪大一点的在滑各种小视频,还有不少人在聊天软件里打字如飞……对着屏幕的表情都很生动,可是一旦抬起头来,似乎马上又换了个人。 后来又有一次,我乘地铁的时候,因为比较拥挤,一个小伙子倒退时踩了我一脚,他大概意识到了,很快把脚挪开,脸上闪过一丝不安,马上又恢复正常,我也没有计较。不幸的是,过了十来分钟,他又踩了我一脚,同样是先有一点不安,很快又恢复正常。 这次我忍不了了,于是我开口告诉他:“小伙子,你已经踩了我两脚了。” (more…)

3 days ago

First name, last name, middle name,浅谈外国人名

前几天,国内朋友发来一条消息,原来是乌克兰F-16坠落,飞行员丧生的新闻。我本来以为他要讨论此事的真假和原委,他真正的问题却完全出乎我的意料: 新闻里说,飞行员叫阿列克谢·“月鱼”·梅斯,对应原文是Alexei “Moonfish” Mes,为什么会有人把“月鱼”写在自己的名字里,而且还打引号。 之前看新闻,乌克兰还有一个著名的飞行员叫安德烈·“果汁”·皮尔希科夫(Andrii “Juice” Pishchykov),怎么“果汁”也是正式的名字? 未必Moonfish和Juice之类,还有什么特别的含义吗?…… 这堆问题看的我有点想笑,因为自己以前也很苦恼外国人的名字,只有在国外长期生活,才逐渐搞清楚这其中的名堂。所以,除了解答朋友的问题,我也把自己的解释写下来,搞清楚两个最不容易理解的点,就不会对外国人名有那么多问题了。 (more…)

3 days ago