正则表达式是一种特殊的字符串模式,用于匹配一组字符串。它最早出现于20 世纪40 年代,当时用来描述正则语言,而到20 世纪70 年代才真正出现在程序设计领域。据我所知,正则表达式首次出现在Ken Thompson 编写的QED 文本编辑器中。 “正则表达式是描述一组字符串特征的模式,用来匹配特定的字符串。” ——Ken Thompson 正则表达式后来成为ed、sed 和 vi(vim) 编辑器、 grep、AWK 等Unix 操作系统衍生出的工具集中重要的组成部分。但这些工具实现正则表达式的方式并不完全一致。 本书使用归纳方式讲述,也就是说,会从特例讲起,最终归结到一般情况。所以我们不会先陈述观点,然后举例,而是先为大家展示示例,然后归纳出一般性结论。本书的风格是“实践出真知”。 正则表达式素以“坑多”而闻名,但是这跟你的学习方法有关。大家通常会从下面这个简单的正则表达式开始学习: d ——这是匹配0 到9 范围内的任意数字的字符组简写式——然后过渡到更为复杂的内容,比如: ^((d{3})|^d{3}[.-]?)?d{3}[.-]?d{4}$ 这是本章最后才会讲到的一个还算比较健壮的正则表达式,它可以匹配10 位的北美电话号码。无论区号是否加括号(如果加,则左、右括号必须成对出现),数字间是否有点号(句点)或连字符,它都可以匹配。 第10 章会展示一个更复杂的匹配电话号码的正则表达式,但本章你只要学会上面这个就行了。 现在还不理解也没关系,本章会将整个表达式的内容分拆开一点一点地教给你。只要你跟着这里的示例做(当然包括本书其他示例),编写正则表达式很快就可以习惯成自然。你准备好了吗? 我有时会将本书中的Unicode 字符用它们的代码点(4 位的十六进制数)来表示。这些代码点以U+0000 的形式出现。比如,U+002E 表示点号的代码点。 1.1 从Regexpal开始 首先介绍一下Regexpal 网站( http://www.regexpal.com)。在Google Chrome 或者Mozilla Firefox 之类的浏览器中打开该网站,可以看到如图1-1 所示的网站页面。 图1-1:在谷歌Chrome 浏览器中打开Regexpal 网站 接近浏览器窗口的顶部有一个文本区,下方还有一个更大的文本区。顶部的文本区是用来输入正则表达式的,下方的文本区是用来输入主题或目标文本的。目标文本即要从中匹配字符串的文本。 本书正文每一章的结尾都有一节 “技术备忘录”。这些备忘录提供与所在章节讨论的技术有关的额外信息,并告诉你到哪里可以找到更多的相关信息。这种方法既不会打断正文的节奏,又能为读者多提供一些扩展信息。 1.2 匹配北美电话号码 现在我们要写一个正则表达式匹配北美电话号码。在Regexpal 的下方文本框中键入以下电话号码: 707-827-7019 知道这是哪里的电话号码吗?这是O’Reilly Media 的电话号码。 接下来用正则表达式匹配这个号码。有很多方法都可以做到,但首先只要在上方的文本框键入这个号码本身,也就是与下方文本框的内容完全一致即可(先忍耐一下, 别泄气): 707-827-7019 这时,你应该看到下方文本框中的电话号码从头到尾都以黄色被高亮显示。如果你的结果也是这样(如图1-2 所示),那么一切正常。 本书中提及的有关图片或者屏幕截图中的颜色,比如Regexpal 中的黄色高亮效果,应该只能在线或者从本书的电子版中看到,而不会出现在纸质版本中。如果你在阅读本书的纸质版,我所提及的颜色在你看来将会是灰阶的,对此我非常抱歉。 刚刚你所写的正则表达式是用字符串字面值(string literal)来匹配目标字符串的。所谓字符串字面值,就是字面上看起来是什么就是什么。 现在将上方文本框的号码删除,然后只键入数字7。你看到什么了?现在只有数字7 高亮显示。正则表达式中的字面值(数字)7 与目标文本中数字7 的四个实例匹配。 图1-2:Regexpal 中高亮显示的十位电话号码 1.3 用字符组来匹配数字 如果想同时匹配电话号码中的所有数值或者只匹配特定的数值,该怎么办呢? 在上方的文本框中尝试以下表达式: [0-9] 下方文本框中所有的数值(准确地说,应该是数字)以黄色和蓝色交替高亮显示。正则表达式[0-9] 对正则表达式处理器传递的信息是“匹配0 到9 范围内的任意数字”。 正则表达式将方括号视为特殊的元字符(metacharacter),因此方括号不参与匹配。元字符是在正则表达式中有特殊含义的字符,也是保留字符。[0-9] 这种形式的正则表达式称做字符组( character class),有时也叫字符集(character set)。 可以对数字的范围进行进一步限定。用更具体的一组数字也能得到同样的结果,比如: [012789]什么是正则表达式 | 5 这个字符组只会匹配列出的数字,即0、1、2、7、8、9。在上方的文本框中试一试,下方文本框中的相应数字同样会被交替标亮。 要匹配任意10 位以连字符分隔的北美电话号码,可以使用以下正则表达式: [0-9] [0-9] [0-9]-[0-9] [0-9] [0-9]-[0-9] [0-9] [0-9] [0-9] 这是可以的,但太长了。更好的方法是采用简写形式。 1.4 使用字符组简写式 就像在本章开始看到的那样,d 可以像[0-9] 一样匹配任意阿拉伯数字。请在上方文本框内试一试,和之前的表达式一样,下方的数字都被标亮了。这种正则表达式叫做字符组简写式(character shorthand),也叫转义字符(character escape)。但后一种称谓很容易造成误解,我会尽量不用。至于原因,稍后我再解释。 可以使用以下表达式来匹配电话号码中的任意数字: ddd-ddd-dddd 重复d 三次和四次就可以分别匹配三个和四个数字。该表达式中的连字符是一个字面值,因此会被原样匹配。 除了和上面表达式一样,使用连字符本身(-)来匹配连字符之外,也可以用转义的大写D(D),它匹配任何一个非数字字符。 以下示例使用了D(而没有使用连字符本身)来匹配连字符: dddDdddDdddd 这次整个电话号码包括连字符又都被标亮了。 1.5 匹配任意字符 还可以用点号(.)来匹配那些讨厌的连字符: ddd.ddd.dddd 点号(英文句号)是一个通配符,可以匹配任意字符(但某些情况下不能匹配行起始符)。以上示例中的正则表达式匹配了连字符,但它也可以匹配百分号(%): 或者是竖线(|): 707|827|7019 亦或其他字符。 如前所述,点号一般不匹配行起始符,比如换行符(U+000A)。然而, 有很多方法可以使点号匹配行起始符,之后我会展示。这通常叫做dotall 选项。 1.6 捕获分组和后向引用 本节我们使用捕获分组(capturing group)来匹配电话号码中的某一部分。然后使用后向引用(backreference)对分组中的内容进行引用。要创建捕获分组,先将一个d 放在一对圆括号中,这样就将它放入了一个分组中,后面可以用1 来对捕获的内容进行后向引用: (d)d1 1 对括号内分组捕获的内容进行了反向引用。这个正则表达式匹配的是区号707。以下是对该表达式的详细分析: (d)•匹配第一个数字并将其捕获(数字 7); d•匹配第二个数字(数字 0)但没有捕获,因为没有括号; 1•对捕获的数字进行反向引用(数字 7)。 这个正则表达式只匹配了区号。如果你还没有完全理解,请不要担心。本书后面会介绍很多有关捕获分组的示例。 现在可以用一个分组和几个后向引用对整个电话号码进行匹配: (d)01Ddd1D1ddd 但这还不够简洁美观。下一节我们会尝试更好的方法。 1.7 使用量词 现在用另一种语法来匹配电话号码: d{3}-?d{3}-?d{4} 花括号中的数字表示待查找的数字出现的次数。包含数字的花括号是一种量 (quantifier)。花括号本身用做元字符。 问号是另一种量词,在以上表达式中表示连字符是可选的。也就是说,连字符可以不出现或只出现一次。还有其他的量词,例如加号(+)表示“一个或多个”,星号(*)表示“零个或多个”。 使用量词能让正则表达式变得更简洁: (d{3,4}[.-]?)+ 对,加号表示出现一次或多次。这个正则表达式表示括号里的模式出现一次或多次, 括号里的模式匹配三位或四位数字,后跟一个连字符或一个点号。 你有没有头晕呢?我希望没有。下面逐一解释表达式中的每一项: 左圆括号 •(为捕获分组的起始符; 反斜杠 •为字符组简写式的起始符(对之后的字符进行转义); 字符 •d为字符组简写式的结束符(d匹配 0 到 9 范围内的任意数字); 左花括号 •{为量词起始符; 数字 •3为匹配的最小数量; 逗号 •,隔开不同的数量; 数字 •4为匹配的最大数量; 右花括号 •}为量词的结束符; 左方括号 •[为字符组的起始符; 点号 •.(匹配点号本身); 连字符 •-匹配连字符的本身; 右方括号 •]为字符组的结束符; 问号 •?表示量词“零个或一个”; 右圆括号 •)为捕获分组的结束符; 加号 •+表示量词“一个或多个”。 这个表达式能用但不完全对,因为它只能匹配3 位或4 位的数字,而不管是否符合电话号码的格式。好吧,犯错会让我们印象深刻,进步得更快。 我们来改进一下: (d{3}[.-]?){2}d{4} 这个表达式匹配的字符串是连续两个无括号的三位数字,每三位数字后可以带连字符也可以不带,最后是一个四位数字。8 | 第1 章 1.8 括选文字符 最后这个正则表达式表示第一个3 位数字可以带也可以不带括号,即区号是可选的: ^((d{3})|^d{3}[.-]?)?d{3}[.-]?d{4}$ 为了便于理解,我们再依次看一下表达式中的各项: 出现在正则表达式起始位置或者竖线(•|)之后的脱字符 ^,表示电话号码会出现 在一行的起始位置; 左括号 •(为捕获分组的起始符; (•表示左括号本身; d •匹配一位数字; d•之后的 {3}是量词,表示匹配三位数字; )•匹配右括号本身; 竖线符 •| 表示选择,也就是从多个可选项中选择一个,换句话说,它表示“匹配 一个不带括号的区号或一个带括号的区号”; 脱字符 •^ 匹配行起始位置; d •匹配一位数字; {3}•是表示匹配三位数字的量词; [.-]?•匹配一个可选的点号或连字符; 右括号 •)为捕获分组的结束符; 问号•?表示分组可选,即分组中的前缀可有可无; d •匹配一位数字; {3}•表示匹配三位数字的量词; [.-]?•匹配另一个可选的点号或连字符; d •匹配一位数字; {4}•是表示匹配四位数字的量词; 美元符 •$匹配行结束位置。 这个表达式最终匹配十位的北美电话号码,而且括号、连字符或者点号都是可选的。你可以试试不同格式的电话号码,看看它能否匹配。 以上正则表达式中的捕获分组并不是必需的。分组是必要的,但是捕获不需要。更好的方法是使用非捕获分组。在本书最后一章中我们再次讨论这个正则表达式时,你自然就理解了。什么是正则表达式 | 9 1.9 应用举例 本章最后,我们在几个应用程序里测试一下匹配电话号码的正则表达式。 TextMate 是一个只在Mac 上运行的文本编辑器,它采用与Ruby 语言相同的正则表达式程序库。你可以通过Find(查找)对话框使用正则表达式,如图1-3 示。将Regular expression 旁的复选框选中。 图1-3:在TextMate 中测试正则表达式 Notepad++ 是运行于Windows 上的一个常用的免费文本编辑器,它采用了PCRE (Perl Compatible Regular Expression,Perl 兼容正则表达式) 库。在勾选了Regular expression 旁的单选按钮之后,就可以用正则表达式进行查找和替换了(参见图1-4)。 Oxygen 是个流行且强大的XML 编辑器,它使用Perl 5 的正则表达式语法。可以通过图1-5 中的查找和替换对话框或者通过XML Schema 的正则表达式构建工具使用正则表达式。要在查找和替换对话框里使用正则表达式,则要勾选Regular expression 旁的复选框。10 | 第1 章 图1-4:在Notepad++ 中测试正则表达式 图1-5:在Oxygen 中测试正则表达式 对正则表达式的简介到此为止。恭喜你,你在本章已经接触了不少基础内容。下一章的重点是简单的模式匹配。什么是正则表达式 | 11 1.10 本章所学 什么是正则表达式 • 如何使用简单的正则表达式处理器 Regexpal • 如何匹配字符串字面值 • 如何使用字符组匹配数字 • 如何使用字符组简写式匹配一位数字 • 如何使用字符组简写式匹配一个非数字字符 • 如何使用捕获分组和后向引用 • 如何匹配确切数量的字符串 • 如何匹配出现零次或一次的字符(可选字符)和出现一次或多次的字符 • 如何匹配行起始位置或行结束位置的字符串 • 1.11 相关资源 Regexpal(http://www.regexpal.com)是一个在线的用 JavaScript 实现的正则表达 •式处理器。它并不是正则表达式的完整实现,因此功能并不完整;但是它是个简洁易用的学习工具,很容易上手。 可以从 https://www.google.com/chrome 下载 Chrome 浏览器,或者从 http://www. •mozilla.org/en-US/firefox/new/ 下载Firefox 浏览器。 为什么有那么多编写正则表达式的方法?一个原因是正则表达式具有•可组合性 (composability)。对于一种具备可组合性的语言(James Clark 很好地解释了这种特性, 参见http://www.thaiopensource.com/relaxng/design.html#section5), 不管是形式语言、程序设计语言还是模式语言,都可以很容易地将其原子部分和构造方法用各种不同的方式重新组合。只要掌握了正则表达式的所有“原子部分”, 你就会发现没什么字符串是匹配不出来的。 TextMate 可以从 http://www.macromates.com 获取。有关 TextMate 中正则表达式 •的更多信息,请参考http://manual.macromates.com/en/regular_expressions。 有关 Notepad++ 的更多信息,请参见 http://notepad-plus-plus.org。有关如何 •在Notepad 中使用正则表达式, 请参考http://sourceforge.net/apps/mediawiki/ notepad-plus/index. php?title=Regular_Expressions。 访问 http://www.oxygenxml.com 可以看到更多有关 Oxygen 的内容。有关在查找 •和替换对话框中使用正则表达式的信息,请参见http://www.oxygenxml.com/doc/ ug-editor/topics/find-replace-dialog.html。有关XML Schema 的正则表达式构建工具的信息,请参见http://www.oxygenxml.com/doc/ug-editor/topics/XML-schema-regexp-builder.html。 http://www.ituring.com.cn/book/955?q=%E5%AD%A6%E4%B9%A0%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F