《正则表达式经典实例(第2版)》——2.3 匹配多个字符之一

2.3 匹配多个字符之一

问题描述
创建一个正则表达式来匹配calendar的所有常见的错误拼写形式,使你能够在一份文档中找到这个单词而无需依赖作者的拼写能力。在每个元音位置都允许使用a或者e。创建另外一个正则表达式来匹配一个单个的十六进制字符。再创建一个正则表达式来匹配不属于十六进制字符的单个字符。

本节中的这个问题用于解释一个重要的、经常使用的正则结构—字符组(character class)。

解决方案
错误拼写的calendar

c[ae]l[ae]nd[ae]r
正则选项:无
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby

十六进制字符

[a-fA-F0-9]
正则选项:无
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby

非十六进制字符

[^a-fA-F0-9]
正则选项:无
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby

讨论
使用方括号的表示法被称作是一个字符组(character class)。一个字符组匹配在一个可能的字符列表中的单个字符。在第一个正则表达式中的3个字符组,每组可以匹配一个a或是一个e。它们彼此之间是独立的。当你使用calendar来测试这个正则表达式的时候,第一个字符组匹配a,第二个字符组匹配e,第三个字符组会匹配a。

在一个字符组中,只有4个字符拥有特殊功能:\、^、-和]。如果你使用的是Java或者 .NET,那么左方括号[在字符组中也是一个元字符。反斜杠总是会对紧跟其后的字符进行转义,这与它在字符组之外的作用一样。被转义的字符可以是单个字符,也可以是表示某个范围的开始或结束。另外4个元字符只有当它们被放置到特定位置时才拥有特殊含义。如果不放置在具有特殊含义的位置,那么可以不必在字符组中进行转义,直接把它们作为字面字符来使用。例如,‹[][^-]›就可以在本书中除JavaScript以外所有流派中实现此用法。JavaScript将‹[]›视为空字符组,所以任何情况下都无法匹配。虽然如此,我们还是推荐你始终对这些元字符进行转义,因此前面的正则表达式应该始终使用‹[][^-]›形式。在使用中始终对元字符进行转义会使你的正则表达式更加容易让人理解。

所有其他字符均为字面量,并且可以直接添加到字符组中。正则表达式‹[$()*+.?{|}›匹配方括号中9个字符中的任意一个。这9个字符仅在字符组外有特殊含义。字符组内它们仅视为字面文本。将它们转义只会使正则表达式更加难以阅读。

字母数字字符则不能使用反斜杠来转义。如果这样,要么会出现一个错误,要么会创建一个正则表达式记号(也就是在正则表达式中含有特殊含义的语法符号)。在前面的实例2.2中,我们讨论了一些其他正则表达式记号,其中提到了它们可以在字符组内使用。所有这些记号都由反斜杠和一个字母组成,有时候后面还会跟一些其他字符。因此,‹[\r\n]›会匹配一个回车符(\r)或者换行符(\n)。

如果紧跟着左括号后面是一个脱字符(^)的话,那么就会排除整个字符组。也就是说它会匹配该字符组以外的任意字符。

在本书介绍的所有正则表达式流派中,不包含换行符的排除型字符组都会匹配换行符。请确保你的正则表达式不会无意中匹配多行文本。
连字符(-)被放到两个字符之间的时候就会创建一个范围(range)。该范围包含连字符之前的字符、连字符之后的字符,以及按照字母表顺序位于这两个字符之间的所有字符。要想知道一个范围中到底包含了哪些字符,请查看ASCII或者Unicode字符表。‹[A-z]›包含在ASCII表中大写A到小写z之间的所有字符。注意这个范围中会包含一些标点符号,因此可以使用‹[A-Z[]^_`a-z]›来更加清晰地匹配相同的字符集合。我们推荐你所创建的范围只位于两个数字,或者两个同是大写或者小写的字母之间。

反向的范围,如‹[z-a]›,是不允许的。
变体
简写
由反斜杠和一个字母组成的6个正则表达式记号会构成简写(shorthand)字符组:‹\d›、‹\D›、‹\w›、‹\W›、‹\s›和‹\S›。你可以在字符组之内或者之外使用这些简写。每个小写的简写字符都拥有一个对应的大写简写字符,其含义正好相反。

‹\d›和‹[\d]›都会匹配单个数字。‹\D›会匹配不是数字的任意字符,所以同‹[^\d]›是等价的。

下面是我们使用简写‹\d›重写本例前面“十六进制字符”正则表达式:

[a-fA-F\d]
正则选项:无
正则流派:.NET、Java、PCRE、Perl、Python、Ruby

‹\w›匹配单个的单词字符(word character)。所谓单词字符指的是能够出现在一个单词中的字符。这包括了字母、数字和下划线。这里所选的字符集合看起来可能有些怪异,但是之所以这样选的原因是这些字符正好是在编程语言中的标识符中通常允许使用的字符。‹\W›则会匹配不属于上述字符集合的任意字符。

在Java 4至Java 6、JavaScript、PCRE和Ruby中,‹\w›总是与‹[a-zA-Z0-9]›的含义完全相同。而在 .NET中,它包含来自所有其他字母表(西里尔语、泰语等)的字母和数字。在Java 7中,只有设置了UNICODE\CHARACTER_CLASS标志才会包含其他字母表的字符。在Python 2.x中,只有当你在创建正则表达式时设置了UNICODE或U标志时,才会包含其他字母表的字符。在Python 3.x中,默认包含其他字母表的字符,但可以使用ASCII或A标志使‹\w›只匹配ASCII编码。在Perl 5.14中,/a(ASCII)标志使‹\w›等同于‹[a-zA-Z0-9_]›,而/u(Unicode)标志则加入所有Unicode字母,/l(local)则使‹\w›依所处地区而定。在Perl 5.14之前的版本中,以及在Perl 5.14中使用/d(default)或者未使用/adlu中任意标志时,如果目标文本或正则表达式编码为UTF-8,或者正则表达式包含了255以上码位(如‹\x{100}›)或Unicode属性(如‹\p{L}›),则‹\w›自动包含Unicode字母;否则,‹\w›默认仅匹配ASCII。

在上述这些流派中,‹\d› 遵循与‹\w›相同的规则。在.NET中,其他字母表中的数字总是会被包含进来,而在Python中则依据UNICODE和ASCII标志,以及使用的是Python 2.x还是3.x。在Perl 5.14中,依据/adlu标志。而Perl更早的版本中,依据目标文本和正则表达式的编码,以及正则表达式是否有任何Unicode记号。

‹\s›匹配任意的空白字符(whitespace character)。其中包括了空格、制表符和换行符。‹\S›会匹配‹\s›不能匹配的任意字符。在.NET和JavaScript中,‹\s›也会匹配根据Unicode标准被定义为空白符号的字符。在Java、Perl和Python中,‹\s›遵循与‹\w›和‹\d›相同的规则。需要注意的是,JavaScript对于‹\s›使用Unicode,而对于‹\d›和‹\w›则使用ASCII标准。当我们还要考虑‹\b›的时候,就会遇到更多的不一致性。‹\b›不是一个简写字符组,而是一个字符边界。虽然你可以期望当‹\w›支持Unicode的时候‹\b›也应该支持Unicode,而当‹\w›只支持ASCII的时候,‹\b›也应该是只使用ASCII,然而事实上却不总是如此。在实例2.6中的“单词字符”小节中会介绍更多的细节。

不区分大小写

(?i)[A-F0-9]
正则选项:无
正则流派:.NET、Java、XRegExp、PCRE、Perl、Python、Ruby
(?i)[^A-F0-9]
正则选项:无
正则流派:.NET、Java、XRegExp、PCRE、Perl、Python、Ruby

不区分大小写也会影响字符组,可以使用一个外部选项来设置(参见实例3.4),也可以在正则表达式内采用模式修饰符来设置(参见实例2.1)。上面给出的这两个正则表达式与最初的解答是等价的。

JavaScript也采用相同的规则,但是它并不支持‹(?i)›。要在JavaScript中把一个正则表达式设置为不区分大小写,就需要在创建的时候设置/i标志。或者使用支持正则表达式前设置模式修饰符的JavaScript XRegExp库。

流派特有的特性
.NET字符组补集

[a-zA-Z0-9-[g-zG-Z]]
正则选项:无
正则流派:.NET 2.0或以后版本

这个正则表达式匹配一个十六进制数字,但是采用了一种迂回的方式。这里的基字符组可以匹配任意字母数字字符,但是后面的一个嵌套组则减去了从g到z的所有字母。这个嵌套组必须出现在基组的最后,紧跟在一个连字号之后:‹[class- [subtract]]›。

当采用了Unicode的属性、区块和字母表时,字符组补集(subtraction)会尤为有用。举例来说,‹\p{IsThai}›会匹配在Thai语区块中的任意字符。‹\P{N}›则匹配不拥有Number属性的任意字符。把二者使用字符组差集组合起来,‹[\p{IsThai}-[\P{N}]]›就可以匹配10个泰语数字的任意字符。实例2.7中介绍了Unicode属性更多细节。

Java字符组并集、交集和补集
Java允许把一个字符组嵌套在另外一个组中。如果嵌套的组是直接包含的,则结果是两个字符组的并集(union)。可以嵌套任意多个组。正则表达式‹[a-f[A-F][0-9]]›和‹[a-f[A-F[0-9]]]›使用了字符组并集。它们匹配一个十六进制数字,与不包含额外方块的形式相同。

正则表达式‹[\w&&[a-fA-F0-9\s]]›使用字符组交集匹配十六进制数字。如果举办一个正则表达式混淆大赛,那么上面这个式子很可能会得奖。这里的基本字符组‹\w›可以匹配任意单词字符。嵌套组‹[a-fA-F0-9\s]›则会匹配任意十六进制数字与任意空白字符。最后所得到的组则是这两个组的交集(intersection),只会匹配十六进制数字。因为基组并不匹配空白字符,而嵌套组不能匹配‹[g-zG-Z_]›,因此在最后的字符组中会去掉它们,只保留十六进制数字。

‹[a-zA-Z0-9&&[^g-zG-Z]]›使用字符组补集匹配一个十六进制数字,它同样也采用了一种迂回的方式。这里的基本字符组‹[a-zA-Z0-9]›可以匹配任意字母数字字符,而随后使用了一个嵌套组‹[^g-zG-Z]›来排除从g到z的所有字母。这个嵌套组必须是一个排除型字符组,并且要紧跟在两个&符号之后:‹[class&&[^subtract]]›。

字符组交集和补集在使用到Unicode的属性、区块和字母表时,是非常有用的。依上所述,‹\p{InThai}›会匹配在Thai语区块中的任意字符,而‹\p{N}›则会匹配拥有Number属性的任意字符。那么,使用正则字符组交集‹[\p{InThai}&&[\p{N}]]›就可以匹配10个泰语数字中的任意数字。

时间: 2024-11-05 16:32:58

《正则表达式经典实例(第2版)》——2.3 匹配多个字符之一的相关文章

《正则表达式经典实例(第2版)》——2.2 匹配不可打印字符

2.2 匹配不可打印字符 问题描述 匹配一个包含下列ASCII控制字符的字符串:振铃符(bell).转义符(escape).换页符(form feed).换行符(line feed).回车符(carriage return).水平制表符(horizontal tab)和垂直制表符(vertical tab).这些字符的十六进制ASCII编码分别是07.1B.0C.0A.0D.09.0B. 下面演示了转义序列的用法,以及如何以十六进制编码代表字符. 解决方案 \a\e\f\n\r\t\v 正则选项

《正则表达式经典实例(第2版)》——2.14 消除不必要的回溯

2.14 消除不必要的回溯 问题描述上一个实例解释了贪心和懒惰量词之间的区别,以及它们是如何进行回溯的.在有些情形下,这种回溯是不必要的. ‹bd+b›使用了一个贪心量词,而‹bd+?b›使用的是懒惰量词.它们都会匹配相同的内容:一个整数.如果给它们同样的目标文本,它们都会找到完全一样的匹配.在这里所做的任何回溯都是不必要的.试着改写这个正则表达式,明确地消除所有回溯,使正则表达式更加高效. 解决方案 \b\d++\b 正则选项:无 正则流派:Java.PCRE.Perl 5.10.Ruby 1

《正则表达式经典实例(第2版)》——导读

** 前言 **正则表达式在过去十多年间越来越普及.如今,所有常用的编程语言都会包含一个强大的正则表达式函数库,甚至在语言本身就内嵌了对于正则表达式的支持.许多开发人员都会利用这些正则表达式的功能,在应用程序中为用户提供使用正则表达式对其数据进行查找或者过滤的能力.正则表达式已经无处不在.随着正则表达式的广泛采用,出现了许多相关的著作.其中大多数都很好地讲解了正则表达式的语法,并且还会提供一些例子以及参考实现.然而,我们还没有看到有任何一本书能够针对处理计算机和不同因特网应用上的文本所遇到的各种

《正则表达式经典实例(第2版)》——2.6 匹配完整单词

2.6 匹配完整单词 问题描述创建一个正则表达式来匹配My cat is brown中的cat,同时不能匹配category或是bobcat.再创建一个正则表达式来匹配staccato中的cat,同时不能匹配到上面的3个目标字串. 解决方案单词边界 \bcat\b 正则选项:无 正则流派:.NET.Java.JavaScript.PCRE.Perl.Python.Ruby 非单词边界 \Bcat\B 正则选项:无 正则流派:.NET.Java.JavaScript.PCRE.Perl.Pytho

半小时精通正则表达式 经典实例介绍_正则表达式

开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明 如果您是新手,猜你取消 正则表达式30分钟入门教程"^The": 开头一定要有"The"字符串; "of despair$": 结尾一定要有"of despair" 的字符串; 那么, "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配 "notice": 匹配包含no

半小时精通正则表达式 经典实例介绍

开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明 如果您是新手,猜你取消 正则表达式30分钟入门教程 "^The": 开头一定要有"The"字符串; "of despair$": 结尾一定要有"of despair" 的字符串; 那么, "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配 "notice": 匹配包含n

《正则表达式经典实例(第2版)》——1.3 正则表达式工具

1.3 正则表达式工具 除非已经拥有了相当长的使用正则表达式编程的经验,否则建议你先在一个工具中试验一下正则表达式,而不是直接在源代码中使用它们.本章和第2章中提供的正则表达式示例都是原始正则表达式,其中并不包含编程语言(即使是Unlx shell)所必需的额外的转义符号.因此可以直接把这些正则表达式输入到一个应用程序的查找框中. 第3章讲解如何把正则表达式整合到源代码中.把一个字面的(literal)正则表达式作为一个字符串引用,会让它更加难懂,因为字符串的转义规则会与正则表达式的转义规则混杂

《正则表达式经典实例(第2版)》——第 1 章 正则表达式简介 1.1正则表达式的定义

第 1 章 正则表达式简介 在你打开这本书的时候,很可能已经热切地期望,要在代码中插入本书中找到的那些包含诸多括号和问号的古怪字符串了.如果你已经准备好要"即查即用",我们非常欢迎,第4-9章中会列出并讲解了各种实用的正则表达式. 但是如果阅读本书的前几章,你将为未来节省大量的时间.例如,本章会向读者介绍许多工具-其中一些工具是本书作者之一的Jan所开发的,这些工具可以帮你事先测试和调试正则表达式,而不用等到把它们塞到代码中之后再处理,那时候查找错误就非常困难了.而且开始这几章还会展示

《正则表达式经典实例(第2版)》——2.9 分组和捕获匹配中的子串

2.9 分组和捕获匹配中的子串 问题描述 改进匹配Mary.Jane或Sue的正则表达式,使之只能匹配完整单词.使用分组来实现这个功能,整个正则表达式只需要一对单词分界符,而不是给每个选择分支都使用一对分界符. 创建一个正则表达式,使之匹配yyyy-mm-dd格式的任意日期,并且分别捕获年.月和日.目标是在处理匹配的代码中可以更容易处理这些分别捕获的值.你可以假设目标文本中的所有日期都是合法的.正则表达式不必要考虑去掉像9999-99-99这样的非法数据,因为它们根本不可能出现在目标文本中. 解