Backreference 与 Backtracking

backreference (反向引用)与 backtracking(回溯) 没有关系,它们是两个不同的概念(虽然从英文字面上看有类似之处,但翻译为中文还是宜将它们区分开来--这一点余晟老师是正确的)。

反向引用,指的是在正则表达式中通过 \1、\2 等元序列(或者说是引用变量)引用前面分组的匹配项,以匹配相同的内容。(参见分组、反向引用与编号或命名组

回溯,指的是重复的限定符(+、* )在匹配模式时使用的一种回溯机制,或者说一种回吐机制(这是我个人的非正式说法)。

举个例子:比如在使用模式 ".*- 匹配字符串 "a-bc" 时,正则表达式引擎首先会匹配模式和字符串开始处的 ",然后模式中以限定符 * 限定的句点会匹配剩下的 a-bc"(因为句点匹配除换行符之外的任何字符,而且默认的重复限定符都是贪婪的,会重复尽可能多次 . 元字符)。此时字符串中已经没有可以匹配的字符了,但模式中还有 – 这个组件没有匹配。由于使用重复限定符的模式还有回溯机制,所以此时不能承认匹配失败,而是尝试开始回溯--也就是说此时引擎会逐个让出(give up)业已匹配的字符、首先是最后的 " 与模式中的 – 进行匹配,结果没有匹配;然后引擎再次让出字符 c 与 – 匹配,仍然没有匹配,继续让出字符 b,还是没有匹配。而当让出字符 – 时,结果匹配了。此时模式中的所有组件都成功匹配,整个模式也取得了匹配项--"a-。这个反向倒溯、回吐的过程就是正则表达式中的回溯。

当然,这是在默认情况下,如果使用非贪婪(或懒惰)匹配限定符,即将模式修改为 ".*?-,那么匹配过程就是:正则表达式引擎用模式中的第一个组件 " 匹配字符串初始的直接量字符 ",结果匹配;然后被 ? 限制的 * 限定符,将尽可能少地重复 . 元字符,因为 * 最少匹配零次,所以引擎首先会尝试重复 . 元字符零次,也就是直接用后面 – 匹配 a,结果当然不会匹配。但此时引擎不会就此甘休,而是会启动回溯机制,不过这时不是减少匹配字符,而是向前扩展式的“回溯”,即尝试重复一次 . 元字符,结果 . 元字符与 a 匹配,然后再用 – 匹配 a 后面的 -,结果匹配了。于是,非贪婪限定符在找到匹配项的前提下重复了尽量少的次数(1次),而且,模式中的所有组件都成功匹配,最终整个模式取得了匹配项--"a-。

显然,由于在默认情况下限定符为找到合适的匹配项都有可能要回溯,所以回溯存在占用资源的问题。而这个问题在使用嵌套的限定符时会变得非常突出。所谓嵌套的限定符,是指某个组中包含重复性的限定符,而这个组本身也应用了重复性限定符。因此,也就引出了占有式限定符(Possessive Quantifier)和最小化组(Atomic Group)的概念,这两个概念再谈吧,总之一句话说,成功就成功,失败就失败,不允许重复限定符启动回溯机制。

从以上分析可以看出,回溯与反向引用没有关系,至少是没有直接关系的。所以下面微软中文文档中的例子,虽然同时提到了反向引用和回溯,但只是强调一种可能性而已。

Nonbacktracking Lookahead and Lookbehind 

Positive lookahead and lookbehind do not backtrack. That is, their contents are treated in the same way as the contents of a nonbacktracking (?> ) group.

Because lookahead and lookbehind are always zero-width, backtracking behavior is visible only when capturing groups appear within positive lookahead and lookbehind. For example, the expression (?=(a*))\1a will never find a match because group 1, which is defined within the lookahead, consumes as many “a” characters as there are, then \1a requires one more. Because the lookahead expression is not backtracked, the matching engine does not retry group 1 with fewer “a” characters.

For more on grouping, lookahead, and lookbehind constructs, see Grouping Constructs

我的中文译文是:

非回溯的向前查找及向后查找。

肯定式向前查找和向后查找不进行回溯。换句话说,它们的内容〔注1〕被作为一个非回溯(?>)组来处理。

由于向前查找和向后查找都是零宽度〔注2〕,因此只有当肯定式向前查找和向后查找中存在捕获组时回溯行为才是有可能发生的〔注3〕。例如,表达式 (?=(a*))\1a 永远不会找到匹配项,因为在向前查找中定义的组 1 会占有全部的字符 a,而 \1a 同样也需要匹配一个以上的字符 a。由于向前查找表达式是不进行回溯的,所以匹配引擎〔注4〕不会重新尝试让组 1 匹配更少的字符 a〔注5〕。

要了解有关分组、向前查找和向后查找的更多信息,请参考分组构造

注1:指向前查找或向后查找模式。
注2:即只匹配不返回结果,或者说匹配的是位置。
注3:指可能会在后面反向引用该捕获组时触发回溯--事实上不会发生。
注4:即正则表达式引擎。
注5:即不会(也不可能)进行回溯。

《精通正则表达式》书评

作者:孟岩(原文) 

IT产业新技术日新月异,令人目不暇接,然而在这其中,真正称得上伟大东西的却寥寥无几。1998年,被誉为“软件世界的爱迪生”,发明了 BSD、 TCP/IP、csh、vi 和 NFS 的 SUN 首席科学家 Bill Joy 曾经不无调侃地说,在计算机体系结构领域里,缓存是唯一称得上伟大的思想,其他的一切发明和技术不过是在不同场景下应用这一思想而已。在计算机软件领域里,情形也大体相似。如果罗列这个领域中的伟大发明,我相信绝不会超过二十项。在这个名单当中,当然应该包括分组交换网络、Web、Lisp、哈希算法、UNIX、编译技术、关系模型、面向对象、XML 这些大名鼎鼎的家伙,而正则表达式也绝对不应该被漏掉。正则表达式具有伟大技术发明的一切特点,它简单,优美,功能强大,妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地提高开发效率和程序质量。CSDN 的创始人蒋涛先生在早年开发专业软件产品时,就曾经体验过这一工具的巨大威力,并且一直印象深刻。而我的一位从事网络编辑工作的朋友,最近也领略了正则表达式的威力——他用 Perl 开发了一个不足 20 行的小程序,使用正则表达式将一项原本每天耗用10个人时的工作在一分钟之内自动完成。而正则表达式在生物信息学和人类基因图谱的研究中所发挥的关键作用,更是被传为佳话。无论对于软件开发者,还是从事其他知识工作的专业人士,正则表达式都是最有利的工具之一。

所谓正则表达式,就是一种描述字符串结构模式的形式化表达方法。在发展的初期,这套方法仅限于描述正则文本,故此得名“正则表达式(regular expression)”。随着正则表达式研究的深入和发展,特别是 Perl 语言的实践和探索,正则表达式的能力已经大大突破了传统的、数学上的限制,成为威力巨大的实用工具,在几乎所有主流语言中获得支持。为什么正则表达式具有如此巨大的魅力?一方面,因为正则表达式处理的对象是字符串,或者抽象地说,是一个对象序列,而这恰恰是当今计算机体系的本质数据结构,我们围绕计算机所做的大多数工作,都归结为在这个序列上的操作,因此,正则表达式用途广阔。另 一方面,与大多数其他技术不同,正则表达式具有超强的结构描述能力,而在计算机中,正是不同的结构把无差别的字节组织成千差万别的软件对象,再组合成为无所不能的软件系统,因此,描述了结构,就等于描述了系统。在这方面,正则表达式的地位是独特的。正因为这两点,在现在的软件开发和日常数据处理工作中,正则表达式已经是必不可少的工具。如果一个开发工具不支持正则表达式,那它就会被视为玩具语言,如果一个编辑器不支持正则表达式,那它就会被称为阳春应用。 连人们原本并不指望应用正则表达式的商用数据库,各家厂商也竞相以支持正则表达式为卖点。正则表达式的声势之隆,是毋庸置疑的。

非常奇怪的是,这样一个了不起的技术,在我国却并没有得到充分推广。以其价值而言,正则表达式不但值得每一个专业程序员掌握,而且值得所有知识工作者去了 解。然而现实情况是,不但一般知识工作者大多闻所未闻,很多专业程序员也视之为畏途。为什么会出现这种情况呢?原因有二。其一,正则表达式产生和发展在 UNIX 文化体系之中,而我国软件开发社群的知识结构长期受到微软的决定,UNIX 文化影响甚微。在 2002 年推出 .NET 平台之前,微软在其各项主流平台、产品与开发工具当中,均未对正则表达式给予足够重视,相应地,我们的开发者们对正则表达式也就知之不多。第二,也是更重要的原因,就是正则表达式并不是那么好掌握的,在通向驾驭正则表达式强大力量的道路上,还是有那么几只拦路虎的,而要打虎过岗,不但要花点功夫,还要有正确的方法。

学习正则表达式,入门不难,看一些例子,试着模仿模仿,就可以粗通,并且在工作中解决不少问题。然而大部分学习者也就就此止步,他们对自己说:“正则表达式不过如此,我就学到这里了,以后现用现学就行了”。他们以为自己可以像学习其他技术一样,在实践中逐渐提高正则表达式的应用水平。然而事实上,正则表达式并不是每天都会用到,而其密码般的形象,随着时间的推移很容易被忘记,所以经常发生的情况是,开发者对于正则表达式的记忆迅速消退,每次遇到新的问题, 都要查资料,重新唤回记忆,对于稍微复杂一点的问题,只好求助于现成的解决方案。反反复复,长期如此,不但应用水平难以明显提升,而且逐渐对这项技术产生一定的恐惧感和厌烦情绪。这还只是应用阶段,正则表达式应用的高级阶段,要求开发者此外还必须充分理解正则表达式的能力范围,能够将一些正则表达式技术组合应用,达成超乎一般想像的效果。为了高效、正确地解决实际问题,有的时候甚至要求深入理解正则表达式的原理,甚至对于如何实现正则表达式引擎都有所了解,在此基础上,规避陷阱,优化设计,提高程序执行效率。要达到这样的程度,不经过系统的学习是不可能的。

系统学习正则表达式并不是一件容易的事情,仅仅通过阅读一些“HOW TO”的快餐式的文章是不行的,必须有更完整、更系统的资料指导学习。如果你在国外技术社区里询问如何才能系统学习正则表达式,几乎所有的领域专家都会向你推荐一本书——Jeffrey Friedl 的《精通正则表达式》,也就是本书。

这本《精通正则表达式》是系统学习正则表达式的唯一最权威著作。可以说,在今天,如果想理解和掌握正则表达式,想要建立关于这一技术的完整概念体系,想充分发挥其巨大能量,这本书几乎是无法绕开的必经之路。甚至可以说,如果你没有读过这本书,那么你对于正则表达式的理解和应用能力一定达不到升堂入室的程度。本书第一版出于十年之前,自那时起就成为正则表达式领域最全面、最受欢迎的代表著作,数以万计的读者通过这本书掌握了正则表达式,成为行家里手。在任何时候,任何地方,只要提到正则表达式著作,人们都会提到这本书。这本书的质量之高,声誉之盛,使得几乎没有人企图挑战它的地位,从而在正则表达式图书领域形成独特的“一夫当关”的局面,称其为正则表达式圣经,绝对当之无愧。

为什么这本书能够表现得如此出色?我认为这其中有三个原因。其一,作者本人具有多年程序开发经验,理论基础深厚,实战经验丰富,对正则表达式这个主题透彻理解,因此在技术上得心应手,底气十足,对于技术上的难点不回避、不含糊。作者高超的技术水平是本书质量的强大保证。其二,作者思路对头,素材组织得当,用例丰富。正则表达式根植于数学理论,却又能在日常俗事上发挥巨大的效用。写这种类型的技术,思路稍微一偏差,就可能走歪路,不是太理论,就是太琐碎,不是太枯燥,就是太浅薄,实在很难把握。作者清楚地认识到,这本书的读者不是计算机科学家,但也不是满足于“知其然而不知其所以然”的快餐式代码小子,而是具有一定理论素养,却又始终以实践为本的专业开发者。他们需要的是面向实践的理论和思想,是实实在在的实战能力,只有满足这种需要,才能够真正打动读者。 通读此书,可以说作者对这一路线的把握十分成功,保证了内容大方向的正确。其三,这本书的写法独具匠心,堪称典范。技术图书的主要使命是传播专业知识。而专业知识分为框架性知识和具体知识。框架性知识需要通过系统的阅读和学习掌握,而大量的具体知识,则主要通过日常工作的积累以及随用随查的的学习来逐渐填充起来。本书前六章,以顺序式记述的方式,将正则表达式的系统知识娓娓道来,读者像看故事书似的就建立起整个正则表达式的基本知识体系。而后面的内容,则是方便实际开发中频繁查阅之用,包括各大主流语言对正则表达式的支持细节,包含有大量案例。这样的写法,完全符合一般人学习的特点,因此书读起来非常惬意,非常有趣,而用的时候查起来又非常方便。这样的著述风格,实在值得学习。

读者可以在没有任何正则表达式的基础上开始阅读此书,只要勤动脑,加强理解,适当动手练习,将能够在不长的时间里掌握正则表达式的思想和技术精华,这一点已经被很多人验证过,我本人也是这本书的受益者之一。正因为这本书独一无二的地位和高度的可读性,也因为正则表达式作为一项了不起的技术发明所具有的巨大威力,我非常希望更多的读者能够通过认真地学习本书而掌握这一强大技术,并享受阅读的乐趣。

正则表达式的敏感性和特殊性

敏感性和特殊性这两个概念来源于像统计学和流行病学这样的学科中的量化标准。宽泛地说,敏感性可以用实际找到的匹配项中的正确匹配项数除以在匹配全部相关字符序列的情况下应该找到的匹配项数来度量。而特殊性则可以用实际找到的匹配项中的正确匹配项数除以找到的匹配项总数来表示。在使用正则表达式时,敏感性越高,则表明找到的真正匹配项数量越接近要找的全部匹配项;而特殊性越高,则表明找到的匹配项中正确的匹配项越多。

匹分敏感性和特殊性的关键是要明确三个数量:正确的找到的要找的。根据上面的定义,敏感性是指用正确的除以要找的,这个比例越高说明正则表达式越敏感--比如要找 100 个,实际找到了 150 个,而其中有 100 个是正确的,那么敏感性就是 100%;而特殊性则是指用正确的除以找到的,这个比例越高说明正则表达式越特殊<即更具有针对性>--比如上面说找到的 150 个当中有 100 个是正确的,那么特殊性就是 66.67%。所以可以理解为敏感性是从量的角度衡量匹配目标的完成情况,而特殊性则是从质的角度来衡量匹配目标的完成情况。

试验一下:电子邮件地址

1. 打开 PowerGrep,在 Search 文本区域中输入模式 \w*(?<=\w)\.?\w+@.*

2. Folder 文本框中输入文件夹名 C:\BRegExp\Ch09 。如果你把下载的测试文件放在其他地方,请修改这里的路径。

3. File mask 文本框中输入文件名 emailornotemail.txt,并单击 Search 按钮。

4. Results 区域中观察结果,如下图所示。

email_1.gif

这是一次进步,而模式也更加具有特殊性。因为这次没有匹配第 127 8 行中不想要的字符序列。然而,第 3 行中的字符序列 John@somewhere.invalid,并不是一个有效的电子邮件地址。

可以通过将电子邮件地址的域名部分更加特殊化来排除这个不想要的匹配项。众所周知,所有的域名都是字母字符序列后跟一个句点字符,然后跟着三(comnetorg biz)或四(info)个字母字符。在本例中,我们先不考虑 example.co.uk 这样的域名。那么,下面的模式可以作为与刚刚描述的结构对应的一个适当的模式:

\w+\.\w{3,4}

其中 \w+ 甚至会匹配单个字符的域名(.com.net .org 域名允许使用单个字符)。而 \. 转义序列匹配一个句点字符,\w{3,4} 则匹配三或四个字母字符。

将这个模式与前面使用的模式组合起来就是:

\w*(?<=\w)\.?\w+@\w+\.\w{3,4}

5. Search 文本区域中输入模式 \w*(?<=\w)\.?\w+@\w+\.\w{3,4},并单击 Search 按钮。

6. 观察结果。注意第 3 行中不想要的匹配项这次没有匹配。然而,在第 6 行中一个前面没有提及的问题却浮出水面。第 6 行中的电子邮件地址中包含两个 @ 字符,而这是不允许的。

要解决这个问题一种方法是使用向前查找限定在匹配第一个 @ 字符后,不允许后面出现另一个 @ 字符。如果我们继续假设在电子邮件地址中只允许使用字母字符,那么就可以通过向前查找来限定在第一次匹配非字母字符或句点字符之前只有一个 @ 字符。

可以通过下面的模式实现这种限定:

\w*(?<=\w)\.?\w+@(?=[\w\.]+\W)\w+\.\w{3,4}

7. Search 文本区域中把模式修改为 \w*(?<=\w)\.?\w+@(?=[\w\.]+\W)\w+\.\w{3,4},并单击 Search 按钮。

8. 观察结果。结果如下图所示。

email_2.gif

遗憾的是,向前查找并没有解决第 3 行和第 6 行中不想要匹配的问题。我们还需要限定模式要匹配的是一行中的全部文本。换句话说,要添加 ^ 元字符指定一行的开始位置和 $ 元字符指定该行的结束位置。

9. Search 文本区域中把模式修改为 ^\w*(?<=\w)\.?\w+@(?=[\w\.]+\W)\w+\.\w{3,4}$,并单击 Search 按钮。

10. 观察结果。结果如下图所示。

email_3.gif

令人高兴的是,这次成功地排除了第 3 行和第 6 行中不想要的匹配项。至少在这个简单的测试数据文件中,我们获得了 100% 的敏感性和 100% 特殊性。

敏感性和特殊性这两个概念来源于量化研究的科学,比如统计学和流行病学。在这两种学科中,都是用数字来表示敏感性和特殊性的,而且通常使用百分比。因此,对于前面的例子来说,因为使用第一个正则表达式模式时找到了所有正确的电子邮件地址,所以敏感性达到了 100%;而因为 10 个匹配结果中的 6 个(从不是有效的电子邮件地址的角度来说)都是错误的匹配,所以特殊性仅有 40%。而到了该例子的最后,修改后正则表达式的特殊性也上升到了 100%

正则表达式中的位置元字符

^--匹配字符串(或多行模式下行)的开始位置;

$--匹配字符串(或多行模式行下)的结束位置;

\b--匹配位于 \w 字符和 \W 字符之间的位置(又称词边界)。

\B--匹配位于字符之间的位置(即非 \w 字符和 \W 字符之间的位置)。

\A--匹配一个字符串的开始位置。其行为不受多行模式的影响(PHP)。

\z--匹配一个字符串的结束位置。其行为不受多行模式的影响(PHP)。

对于测试字符串 look at this. 而言,在多行模式下,正则表达式 this$ 不会匹配其结尾处的 this,因为 $ 匹配行结尾的位置,而 this 中最后一个字符 s 之后并不是一个换行符,而是一个句点字符。若要匹配这个 this,必须使用正则表达式 this\b ,\b 匹配字母字符和非字母字符之间的位置,而 this 的最后一个字符 s 和其后面的句点字符之间的位置恰好是这样一个位置。

对于 \B 元字符,其用途如下:

如果你想匹配 golden 中的 old 就可以使用模式 \Bold\B ,这个模式匹配一个字符之间的位置后跟字符序列 o、l、d,后跟另一个字符之间的位置。也就是说,\B 不会匹配位于字符和非字符之间的位置,所以模式 \Bold\B 匹配的字符序列 old 一定是被包含在一个单词(或字符串)中间。 

术语 variable substitution 的译法

Perl 正则表达式中可以使用变量,就和在双引号中可以插入变量一样。比如:

在字符串中插入变量

print “$myPattern is found in $myTestString”;

在模式中插入变量

/${myPattern}ll/;(一对正斜杠是模式的默认定界符)

《BRE》中把在模式中插入的变量称为 variable substitution,开始我采用的译法是“变量置入”,后来想改为“置入变量”。比如:“使用置入变量匹配”。但在网上没有搜索到“变量置入”或“置入变量”这样的译法,倒是有人使用“变量替换”--也就是 variable substitution 的直译。虽然使用“变量替换”可以同网上已有的译法一致,但我还是觉得有点不妥。理由如下:

我们常说“使用……”,“使用”后面的省略号应该是名词,以构成动名词结构。而“变量替换”是动名词倒置,“替换”虽具有名词性质,但与“变量”组合起来意思显示不够明确--因为容易让人理解为“变量替换过程”。而事实上,使用的是变量,而不是使用替换过程。

所以,我觉得应该在“置入变量”、“替换变量”甚至“插入变量”中选择一个。这样当在书中其他内容中提及“使用置入变量”、“使用替换变量”甚至“使用插入变量”时,意思更明确,不致混淆。

Math、Group 和 Capture

.NET 平台中的正则表达式包含在 System.Text.RegularExpressions 命名空间中。该命名空间中的类主要有:

  • Regex
  • MatchCollection
  • Match
  • GroupCollection
  • Group
  • CaptureCollection
  • Capture

其中,Regex 类是核心。

Regex.Match() 方法返回一个包含(或不包含)匹配项信息的 Match 对象;

Regex.Matches() 方法则返回包含所有匹配项的 MatchCollection (Match 对象的集合)对象;

Match.Groups 属性中包含着 GroupCollection (Group 对象的集合);

Group.Captures 属性中包含着 CaptureCollection(Capture 对象的集合)。

而 MatchCollection、GroupCollection 和 CaptureCollection 中包含的 Match、Group 和 Capture 对象可以通过 For Each 语句来枚举。

下面的示例代码涉及到了以上所有的类、方法和属性的使用:

Imports System.Text.RegularExpressions Module Module1   Sub Main()     Dim myRegex = New Regex(“([A-Z])+(d)+”)     Console.WriteLine(“Enter a string on the following line:”)     Dim inputString = Console.ReadLine()     Dim myMatchCollection = myRegex.Matches(inputString)     Console.WriteLine()     Console.WriteLine(“There are {0} matches.”, myMatchCollection.Count)     Console.WriteLine()     Dim myMatch As Match     Dim myGroupCollection As GroupCollection     Dim myGroup As Group     For Each myMatch In myMatchCollection       Console.WriteLine(“At position {0}, the match ‘{1}’ was found”,         myMatch.Index, myMatch.ToString)       Console.WriteLine(“This match has {0} groups.”,         myMatch.Groups.Count)       myGroupCollection = myMatch.Groups       For Each myGroup In myGroupCollection         Dim myCaptureCollection As CaptureCollection = myGroup.Captures         Dim myCapture As Capture         Console.WriteLine(“Group containing ‘{0}’ found at position           ‘{1}’.”, myGroup.Value, myGroup.Index)         For Each myCapture In myCaptureCollection           Console.WriteLine(“ Capture: ‘{0}’ at position ‘{1}’.”,             myCapture.Value, myCapture.Index)         Next       Next       Console.WriteLine()     Next   Console.WriteLine()   Console.WriteLine(“Press Return to close this application.”)   Console.ReadLine() End Sub End Module

在命令行中执行以上代码,并根据提示输入测试字符串:ABC1 A123(注意中间的空格),按回车后观察如下图所示的执行结果:
Match-Group-Capture

更直观的示意图如下所示:

bre_vbnet2.gif

能够反映动态匹配、组、和捕获过程的示意图如下:

match-group-capturenet3.gif

原理剖析:

本例中使用的正则表达式模式是:([A-Z])+(\d)+。这是一个精心设计的模式,为什么不把 + 限定放在分组的圆括号中呢?目的就是为了验证对多个匹配的字符创建多个分组和多个捕获的效果。如果把模式修改为 ([A-Z]+)(\d+) 那结果就完全不一样了--对我们理解组和捕获之间的关系就没有帮助了(若不理解,请继续看下面的分析)。

.NET 中对正则表达式匹配项进行了细分,即分为匹配(由 Match 对象表示)、组(由 Group 对象表示)和捕获(由 Capture 对象来表示)三个层次。这三个层次是“父子”关系,或者说具有包含关系,即一个匹配中包含所有组,一个组中包含所有捕获。当然,捕获是最小单位,可以最小化到捕获一个字符--比如本例模式中的 ([A-Z]) 这个组每次就能捕获到一个大写字母(如果有)。这种关系通过上面第二幅插图能够看得非常清楚。

在匹配项是 ABC1 时,匹配(Match)中包含着 ABC1 这个字符序列,而与该匹配对应的第0组(Group[0])中包含同样的字符序列,与第0组对应的第0个捕获(Capture[0])中也是如此。而与第一个分组对应的第1组(Group[1])会根据圆括号中的模式 [A-Z] 匹配三次--分别是 A、B 和 C。与此同时,将捕获到的字符分别保存在 Capture[0]、Capture[1] 和 Capturep[2] 中。注意,虽然捕获到了三个字符,但在第1组中每次都会用新捕获的字符替换上一次捕获的字符(即将上一次保存在 Group[1] 中的值丢弃。如上面第三幅插图所示),最终当匹配到数字 1 之前的位置时,Group[1] 中保存的是大写 C。这就是根据多个匹配项,创建多个分组和多个捕获的过程。

由此可见,在依据同一分组中的模式进行多次匹配时,只有通过 Capture 才能保存每次匹配的结果。这正是 .NET 中对文本操作精细化的一个解决方案。虽然通过多创建分组也能够使组中保存更少的字符(甚至单个字符),但毕竟不如增加一个新的 Capture 概念来得更直接(容易理解)、也更方便(例如可以通过编程遍历)。

Mastering Regular Expressions,3rd

《Mastering Regular Expressions,3rd》(以下简称《MRE3》)这本书的中译版也要上市了,中文名叫《精通正则表达式》,由电子工业出版社的博文视点推出。而《Beginning Regular Expressions》(以下简称《BRE》)可能还要等四个月以上才能面世。

本文的目的是就这两本“国内首册”正则表达式技术书的引进版作一比较。

首先,来看一下《MRE3》的英文目录(PDF下载):

Preface
1 Introduction to Regular Expressions
    Solving Real Problems
    Regular Expressions as a Language
        The Filename Analogy
        The Language Analogy
    The Regular-Expression Frame of Mind
        If You Have Some Regular-Expression Experience
        Searching Text Files: Egrep
    Egrep Metacharacters
        Start and End of the Line
        Character Classes
        Matching Any Character with Dot
        Alternation
        Ignoring Differences in Capitalization
        Word Boundaries
        In a Nutshell
        Optional Items
        Other Quantifiers: Repetition
        Parentheses and Backreferences
        The Great Escape
    Expanding the Foundation
        Linguistic Diversification
        The Goal of a Regular Expression
        A Few More Examples
        Regular Expression Nomenclature
        Improving on the Status Quo
        Summary
    Personal Glimpses
2 Extended Introductory Examples
    About the Examples
        A Short Introduction to Perl
    Matching Text with Regular Expressions
        Toward a More Real-World Example
        Side Effects of a Successful Match
        Intertwined Regular Expressions
        Inter mission
    Modifying Text with Regular Expressions
        Example: Form Letter
        Example: Prettifying a Stock Price
        Automated Editing
        A Small Mail Utility
        Adding Commas to a Number with Lookaround
        Text-to-HTML Conversion
        That Doubled-Word Thing
3 Over view of Regular Expression Features and Flavors
    A Casual Stroll Across the Regex Landscape
        The Origins of Regular Expressions
        At a Glance
    Care and Handling of Regular Expressions
        Integrated Handling
        Procedural and Object-Oriented Handling
        A Search-and-Replace Example
        Search and Replace in Other Languages
        Care and Handling: Summary
    Strings, Character Encodings, and Modes
        Strings as Regular Expressions
        Character-Encoding Issues
        Unicode
        Regex Modes and Match Modes
    Common Metacharacters and Features
        Character Representations
        Character Classes and Class-Like Constructs
        Anchors and Other “Zero-Width Assertions”
        Comments and Mode Modifiers
        Grouping, Capturing, Conditionals, and Control
    Guide to the Advanced Chapters
4 The Mechanics of Expression Processing
    Start Your Engines!
        Two Kinds of Engines
        New Standards
        Regex Engine Types
        From the Department of Redundancy Department
        Testing the Engine Type
    Match Basics
        About the Examples
        Rule 1: The Match That Begins Earliest Wins
        Engine Pieces and Parts
        Rule 2: The Standard Quantifiers Are Greedy
    Regex-Directed Versus Text-Directed
        NFA Engine: Regex-Directed
        DFA Engine: Text-Dir ected
        First Thoughts: NFA and DFA in Comparison
    Backtracking
        A Really Crummy Analogy
        Two Important Points on Backtracking
        Saved States
        Backtracking and Greediness
    More About Greediness and Backtracking
        Problems of Greediness
        Multi-Character “Quotes”
        Using Lazy Quantifiers
        Greediness and Laziness Always Favor a Match
        The Essence of Greediness, Laziness, and Backtracking
        Possessive Quantifiers and Atomic Grouping
        Possessive Quantifiers, ?+, ++, ++, and {m,n}+
        The Backtracking of Lookaround
        Is Alternation Greedy?
        Taking Advantage of Ordered Alternation
    NFA, DFA, and POSIX
        “The Longest-Leftmost”
        POSIX and the Longest-Leftmost Rule
        Speed and Efficiency
        Summary: NFA and DFA in Comparison
    Summary
5 Practical Regex Techniques
    Regex Balancing Act
    A Few Short Examples
        Continuing with Continuation Lines
        Matching an IP Addr ess
        Working with Filenames
        Matching Balanced Sets of Parentheses
        Watching Out for Unwanted Matches
        Matching Delimited Text
        Knowing Your Data and Making Assumptions
        Stripping Leading and Trailing Whitespace
    HTML-Related Examples
        Matching an HTML Tag
        Matching an HTML Link
        Examining an HTTP URL
        Validating a Hostname
        Plucking Out a URL in the Real World
    Extended Examples
        Keeping in Sync with Your Data
        Parsing CSV Files
6 Crafting an Efficient Expression
    A Sobering Example
        A Simple Change—Placing Your Best Foot Forward
        Efficiency Versus Correctness
        Advancing Further—Localizing the Greediness
        Reality Check
    A Global View of Backtracking
        More Work for a POSIX NFA
        Work Required During a Non-Match
        Being More Specific
        Alternation Can Be Expensive
    Benchmarking
        Know What You’r e Measuring
        Benchmarking with PHP
        Benchmarking with Java
        Benchmarking with VB.NET
        Benchmarking with Ruby
        Benchmarking with Python
        Benchmarking with Tcl
    Common Optimizations
        No Free Lunch
        Everyone’s Lunch is Different
        The Mechanics of Regex Application
        Pre-Application Optimizations
        Optimizations with the Transmission
        Optimizations of the Regex Itself
    Techniques for Faster Expressions
        Common Sense Techniques
        Expose Literal Text
        Expose Anchors
        Lazy Versus Greedy: Be Specific
        Split Into Multiple Regular Expressions
        Mimic Initial-Character Discrimination
        Use Atomic Grouping and Possessive Quantifiers
        Lead the Engine to a Match
    Unrolling the Loop
        Method 1: Building a Regex From Past Experiences
        The Real “Unrolling-the-Loop” Pattern
        Method 2: A Top-Down View
        Method 3: An Internet Hostname
        Observations
        Using Atomic Grouping and Possessive Quantifiers
        Short Unrolling Examples
        Unrolling C Comments
    The Freeflowing Regex
        A Helping Hand to Guide the Match
        A Well-Guided Regex is a Fast Regex
        Wrapup
    In Summary: Think!
7 Perl
    Regular Expressions as a Language Component
        Perl’s Greatest Strength
        Perl’s Greatest Weakness
    Perl’s Regex Flavor
        Regex Operands and Regex Literals
        How Regex Literals Are Parsed
        Regex Modifiers
    Regex-Related Perlisms
        Expression Context
        Dynamic Scope and Regex Match Effects
        Special Variables Modified by a Match
    The qr/˙˙˙/ Operator and Regex Objects
        Building and Using Regex Objects
        Viewing Regex Objects
        Using Regex Objects for Efficiency
    The Match Operator
        Match’s Regex Operand
        Specifying the Match Target Operand
        Different Uses of the Match Operator
        Iterative Matching: Scalar Context, with /g
        The Match Operator’s Environmental Relations
    The Substitution Operator
        The Replacement Operand
        The /e Modifier
        Context and Return Value
    The Split Operator
        Basic Split
        Returning Empty Elements
        Split’s Special Regex Operands
        Split’s Match Operand with Capturing Parentheses
    Fun with Perl Enhancements
        Using a Dynamic Regex to Match Nested Pairs
        Using the Embedded-Code Construct
        Using local in an Embedded-Code Construct
        A Warning About Embedded Code and my Variables
        Matching Nested Constructs with Embedded Code
        Overloading Regex Literals
        Problems with Regex-Literal Overloading
        Mimicking Named Capture
    Perl Efficiency Issues
        “Ther e’s Mor e Than One Way to Do It”
        Regex Compilation, the /o Modifier, qr/˙˙˙/, and Efficiency
        Understanding the “Pre-Match” Copy
        The Study Function
        Benchmarking
        Regex Debugging Information
    Final Comments
8 Java
    Java’s Regex Flavor
        Java Support for \p{˙˙˙} and \P{˙˙˙}
        Unicode Line Terminators
    Using java.util.regex
    The Pattern.compile() Factory
        Pattern’s matcher method
    The Matcher Object
        Applying the Regex
        Querying Match Results
        Simple Search and Replace
        Advanced Search and Replace
        In-Place Search and Replace
        The Matcher’s Region
        Method Chaining
        Methods for Building a Scanner
        Other Matcher Methods
    Other Pattern Methods
        Pattern’s split Method, with One Argument
        Pattern’s split Method, with Two Arguments
    Additional Examples
        Adding Width and Height Attributes to Image Tags
        Validating HTML with Multiple Patterns Per Matcher
        Parsing Comma-Separated Values (CSV) Text
    Java Version Differences
        Differences Between 1.4.2 and 1.5.0
        Differences Between 1.5.0 and 1.6
9 .NET
    .NET’s Regex Flavor
        Additional Comments on the Flavor
    Using .NET Regular Expressions
        Regex Quickstart
        Package Overview
        Core Object Overview
    Core Object Details
        Creating Regex Objects
        Using Regex Objects
        Using Match Objects
        Using Group Objects
    Static “Convenience” Functions
        Regex Caching
    Support Functions
    Advanced .NET
        Regex Assemblies
        Matching Nested Constructs
        Capture Objects
10 PHP
    PHP’s Regex Flavor
    The Preg Function Interface
        “Pattern” Arguments
    The Preg Functions
        preg_match
        preg_matchRall
        preg_replace
        preg_replaceRcallback
        preg_split
        preg_grep
        preg_quote
    “Missing” Preg Functions
        preg_regex_to_pattern
        Syntax-Checking an Unknown Pattern Argument
        Syntax-Checking an Unknown Regex
    Recursive Expressions
        Matching Text with Nested Parentheses
        No Backtracking Into Recursion
        Matching a Set of Nested Parentheses
    PHP Efficiency Issues
        The S Pattern Modifier: “Study”
    Extended Examples
        CSV Parsing with PHP
        Checking Tagged Data for Proper Nesting
Index

页面: 1 2 3 4 5 6

模式-模式-模式

“模式:事物的标准样式”。这是汉语词典中的解释,但在英语环境下--特别是在计算机编程领域中,“模式”一般也指“事物的标准样式”,比如“设计模式(design pattern)”,就是指“设计的标准样式”。但在正则表达式语境下,“模式”除对应 pattern 之外还对应着 mode 和 schema:

Pattern(模式):表示正则表达式(可以作为正则表达式的代称)。因为一个正则表达式可能会匹配多个字符序列,而可以认为所有匹配的字符序列都具有相同的模式(即“标准样式”,或符合“标准格式”)。

Mode(模式):表示匹配的方式,比如以不区分大小写的方式匹配等。也称为匹配模式。

Schema(模式):表示 W3C XML Schema(严格来讲,这个“模式”与正则表达式无关)。而 W3C XML Schema 译为“模式”之所以有意义,是因为一份 XML Schema 文档规定了一类 XML 实例文档的标准样式--包括标记构成和元素及属性取值的范围。在 W3C XML Schema 中,正则表达式的用途就是用来限制元素及属性可取值的类型和范围。比如,要限制某元素的 number 属性只能是一位数字,就可以通过 <xs:pattern value=”\d” /> 来表示。

字符类和元字符

有很多常见的字符类(或取反的字符类)存在等价的元字符。比如:

\w [A-Za-z0-9_]
\W [^A-Za-z0-9_]
\d [0-9]
\D [^0-9]
\s [fnrtv])
\S [^fnrtv])

另外,在使用字符类时应该注意以下问题:

  • [a-Z] 并不等同于 [A-Za-z]。因为在 ASCII 和 Unicode 字符集中,大写和小写的英文字母字符并不是连续存在的。事实上,在大写的 Z 的后面和小写 a 的前面有六个字符,而且全都不是字母字符。它们分别是 [(左方括号)、\(反斜杠)、](右方括号)、^(脱字符)、_(下划线)和 `(重音符);
  • 表示对字符类取反的 ^ 字符只有位于左方括号直接后面时才表示取反的意思。否则,只匹配其自身;
  • 表示范围的 – 字符,如果要通过字符类来匹配其自身时,注意不能用在字符之间,否则 – 仍然表示范围。

字符类与圆括号中交替选择的选择性

字符类,顾名思义就是某一类字符或者将某些字符分成一类。正则表达式中的字符类用一对方括号([])来表示,比如 [abcd] 或 [0123456789]。事实上,字符类所暗含的意思就是选择性,即“多选一”――不管方括号中包含多少个字符,一对方括号一次只能匹配一个字符。

在正则表达式中,更明显具有选择性的就是交替选择(alternation)。交替选择以竖线(|)表示,竖线两侧的模式可以称之为选项。交替选择必须与一对圆括号连用,即通过圆括号将所有以竖线分隔的选项包围起来。交替选择模式中至少要有两个选项(一个竖线)――此时的选择性可以称之为“二选一”,但也可以包含多个选项――此时的选择性则可以称之为“多选一”。但这里的“多选一”与字符类中的“多选一”不同,这里是指“从多个选项中的选择一个选项”,而不是“从多个字符中选择一个字符”。而选项则可以是任何正则表达式模式。

举例来说,如果用字符类

[a-z]

来测试字符串 abcde,那每次只能返回一个字母,分别是 a、b、c、d 和 e。而如果用交替选择

(abc|de)

来测试同一个字符串,那么(每次都)将返回字符序列 abc。而如果使用另一个交替选择

(\w+|abc)

来测试这个字符串,那么将返回字符序列 abcde。

现在,我们对字符类与交替选择中的选择性就应该非常清楚了。