---文章转自http://www.officefans.net/cdb/viewthread.php?tid=86824&extra=page%3D1---
1、何谓正则表达式
正则表达式(regular expression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征,即一段字符串的模式。比如,表达式“ab+” 描述的特征是“一个 'a' 和 任意个 'b' ”,那么 'ab', 'abb', 'abbbbbbbbbb' 都符合这个特征。
正则表达式的功能非常强大,打个比方,比如在关系数据库中,SQL语言的地位是显赫的,功能是强大的,那么在字符串处理这个领域,正则表达式可以和SQL语言在关系数据库中扮演的角色相媲美。从我本人从事多年的软件开发实践中可以感觉到,正则表达式是值得每一个从事计算机相关工作的人去学习、掌握,我们可以从中受惠。
正则表达式可以用来:(1)验证字符串是否符合指定特征,比如验证是否是合法的邮件地址。(2)用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便。(3)用来替换,比普通的替换更强大。(4)split字符串。
正则表达式学习起来其实是很简单的,不多的几个较为抽象的概念也很容易理解。之所以很多人感觉正则表达式比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难;另一方面,各种引擎自带的文档一般都要介绍它特有的功能,然而这部分特有的功能并不是我们首先要理解的。
平时的开发,常常在Java和.NET平台下进行,这两个平台都提供了相应的正则表达式引擎,用起来感觉也很好。在VBA的开发中,尽管Excel中提供了很多的内置函数,帮助我们解决一些字符串处理相关的问题,但对有些情况,用内置的函数或者编程来解决,常常会感觉到力不从心或者非常繁琐,而用正则表达式来解决,却变得非常简单。所有我们在这里主要学习如何在VBA中使用正则表达式。
1、何谓正则表达式
正则表达式(regular expression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征,即一段字符串的模式。比如,表达式“ab+” 描述的特征是“一个 'a' 和 任意个 'b' ”,那么 'ab', 'abb', 'abbbbbbbbbb' 都符合这个特征。
正则表达式的功能非常强大,打个比方,比如在关系数据库中,SQL语言的地位是显赫的,功能是强大的,那么在字符串处理这个领域,正则表达式可以和SQL语言在关系数据库中扮演的角色相媲美。从我本人从事多年的软件开发实践中可以感觉到,正则表达式是值得每一个从事计算机相关工作的人去学习、掌握,我们可以从中受惠。
正则表达式可以用来:(1)验证字符串是否符合指定特征,比如验证是否是合法的邮件地址。(2)用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便。(3)用来替换,比普通的替换更强大。(4)split字符串。
正则表达式学习起来其实是很简单的,不多的几个较为抽象的概念也很容易理解。之所以很多人感觉正则表达式比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难;另一方面,各种引擎自带的文档一般都要介绍它特有的功能,然而这部分特有的功能并不是我们首先要理解的。
平时的开发,常常在Java和.NET平台下进行,这两个平台都提供了相应的正则表达式引擎,用起来感觉也很好。在VBA的开发中,尽管Excel中提供了很多的内置函数,帮助我们解决一些字符串处理相关的问题,但对有些情况,用内置的函数或者编程来解决,常常会感觉到力不从心或者非常繁琐,而用正则表达式来解决,却变得非常简单。所有我们在这里主要学习如何在VBA中使用正则表达式。
2、相关工具
工欲善其事,必先利其器,这是我们在使用正则表达式需要注意的地方,我们做任何事情,若善于利用一些有用的辅助工具,常常可以达到事半功倍的效果。那么在使用正则表达式的时候也是一样,我们需要一些辅助工具来帮助我们更快、更好地完整任务。我在使用了多种正则表达式辅助开发工具后,经过综合地比较、评价,最终锁定的一款利器,堪比关羽手中的“青龙偃月刀”,所向披靡。
这款利器就是 RegexBuddy,相关介绍参见 www.regexbuddy.com,这是一款共享工具,功能上没有限制,但只能用7天,要money的,需要注册,不过大家在网上找regexbuddy 2.3.2 full version。
另外,在今年8月份,对于当时最新的版本 3.0.3已被本人一不小心crack了,想用的可以给我发邮件,linuxyf@gmail.com,邮件中要注明(Office精英俱乐部)。
下面我们粗略地看一下RegexBuddy有哪些主要功能:
1、可以很容易地创建你想要的正则表达式。
2、可以帮助我们清楚地理解别人写的正则表达式的含义。
3、可以用测试字符串或者测试文件验证正则表达式是否合乎要求。
4、自动生成对应各种编程语言的程序代码段,我们只需要写出符号要求的正则表达式,对应编程语言的代码段立刻可以生成,这一点非常的方便。
5、有一个可复用的正则表达式库,放置的都是一些经典的、常用的正则表达式供我们所用。
从上面的主要功能介绍我们可以看出,RegexBuddy绝对是我们处理正则表达式的不二选择,是一款出色的正则表达式伴侣式工具,郑重推荐大家使用。
这款利器就是 RegexBuddy,相关介绍参见 www.regexbuddy.com,这是一款共享工具,功能上没有限制,但只能用7天,要money的,需要注册,不过大家在网上找regexbuddy 2.3.2 full version。
另外,在今年8月份,对于当时最新的版本 3.0.3已被本人一不小心crack了,想用的可以给我发邮件,linuxyf@gmail.com,邮件中要注明(Office精英俱乐部)。
下面我们粗略地看一下RegexBuddy有哪些主要功能:
1、可以很容易地创建你想要的正则表达式。
2、可以帮助我们清楚地理解别人写的正则表达式的含义。
3、可以用测试字符串或者测试文件验证正则表达式是否合乎要求。
4、自动生成对应各种编程语言的程序代码段,我们只需要写出符号要求的正则表达式,对应编程语言的代码段立刻可以生成,这一点非常的方便。
5、有一个可复用的正则表达式库,放置的都是一些经典的、常用的正则表达式供我们所用。
从上面的主要功能介绍我们可以看出,RegexBuddy绝对是我们处理正则表达式的不二选择,是一款出色的正则表达式伴侣式工具,郑重推荐大家使用。
3、正则表达式规则------字符
3.1 普通字符
字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是"普通字符"。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。
举例1:表达式 "c",在匹配字符串 "abcde" 时,匹配结果是:成功;匹配到的内容是:"c";匹配到的位置是:开始于2,结束于3。(注:下标从0开始还是从1开始,因当前编程语言的不同而可能不同)
举例2:表达式 "bcd",在匹配字符串 "abcde" 时,匹配结果是:成功;匹配到的内容是:"bcd";匹配到的位置是:开始于1,结束于4。
3.2 特殊字符
因为我们不总是做简单搜索,所以需要保留一些字符用作特殊用途。有11个字符有特殊的含义,分别是:"[", "\","^","$",".","|","?","*","+","(" 和")"。 这些特殊字符我们通常称作元字符。
如果你想把它们作为一个普通字符来使用,需要在这些符号之前加一个转义字符"\",比如,如果你想匹配"1+1=2",正确的模式应该是"1\+1=2",不然,加号"+"就有了特殊的含义。注意,"1+1=2",其中的加号不带转义字符,但这也是一个有效的正则表达式,所以在程序执行的时候不会有错误,但它不会匹配"1+1=2",因为特殊字符"+",它将会在"123+111=234"字符串中,匹配"111=2"。
3.3 不能显示的字符
模式串中可以使用一些不不能显示的字符,如:用 \t 来匹配一个 tab 字符 (ASCII 0x09), \r 代表回车 (0x0D) , \n 代表换行 (0x0A). 注意windows操作系统的文本文件中,用 \r\n 分割行,而UNIX 操作系统中使用\n.
字母、数字、汉字、下划线、以及后边章节中没有特殊定义的标点符号,都是"普通字符"。表达式中的普通字符,在匹配一个字符串的时候,匹配与之相同的一个字符。
举例1:表达式 "c",在匹配字符串 "abcde" 时,匹配结果是:成功;匹配到的内容是:"c";匹配到的位置是:开始于2,结束于3。(注:下标从0开始还是从1开始,因当前编程语言的不同而可能不同)
举例2:表达式 "bcd",在匹配字符串 "abcde" 时,匹配结果是:成功;匹配到的内容是:"bcd";匹配到的位置是:开始于1,结束于4。
3.2 特殊字符
因为我们不总是做简单搜索,所以需要保留一些字符用作特殊用途。有11个字符有特殊的含义,分别是:"[", "\","^","$",".","|","?","*","+","(" 和")"。 这些特殊字符我们通常称作元字符。
如果你想把它们作为一个普通字符来使用,需要在这些符号之前加一个转义字符"\",比如,如果你想匹配"1+1=2",正确的模式应该是"1\+1=2",不然,加号"+"就有了特殊的含义。注意,"1+1=2",其中的加号不带转义字符,但这也是一个有效的正则表达式,所以在程序执行的时候不会有错误,但它不会匹配"1+1=2",因为特殊字符"+",它将会在"123+111=234"字符串中,匹配"111=2"。
3.3 不能显示的字符
模式串中可以使用一些不不能显示的字符,如:用 \t 来匹配一个 tab 字符 (ASCII 0x09), \r 代表回车 (0x0D) , \n 代表换行 (0x0A). 注意windows操作系统的文本文件中,用 \r\n 分割行,而UNIX 操作系统中使用\n.
4、正则表达式引擎的内部工作机制
了解正则表达式引擎的内部工作机制将有助于你更好地驾驭正则表达式,高效、简洁书写合理的正则表达式来完成任务。
有两种类型的引擎:文本导向(text-directed)引擎和正则导向(regex-directed)引擎。Jeffrey Friedl把它们称作DFA和NFA引擎。本文谈到的是正则导向引擎。因为一些非常有用的特性,如“惰性”量词(lazy quantifiers)和反向引用(backreferences),只能在正则导向的引擎中实现, 毫不意外这种引擎是目前最流行的引擎。
你可以轻易分辨所使用的引擎是哪一种,若反向引用或“惰性”量词被实现,则可以肯定你使用的是正则导向引擎。你可以作如下测试:将正则表达式<<regex|regex not>>应用到字符串“regex not”。如果匹配的结果是regex,则是正则导向引擎。如果结果是regex not,则是文本导向引擎。
正则导向的引擎总是返回最左边的匹配
这是非中重要的一点:即使在后边可能发现一个“更好”的匹配,正则导向的引擎也总是返回最左边的匹配。
当把<<cat>>应用到“He captured a catfish for his cat”,引擎先比较<<c>>和“H”,结果失败了。于是引擎再比较<<c>>和“e”,也失败了。直到第四个字符,<<c>>匹配了“c”。<<a>>匹配了第五个字符。到第六个字符<<t>>没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配。
有两种类型的引擎:文本导向(text-directed)引擎和正则导向(regex-directed)引擎。Jeffrey Friedl把它们称作DFA和NFA引擎。本文谈到的是正则导向引擎。因为一些非常有用的特性,如“惰性”量词(lazy quantifiers)和反向引用(backreferences),只能在正则导向的引擎中实现, 毫不意外这种引擎是目前最流行的引擎。
你可以轻易分辨所使用的引擎是哪一种,若反向引用或“惰性”量词被实现,则可以肯定你使用的是正则导向引擎。你可以作如下测试:将正则表达式<<regex|regex not>>应用到字符串“regex not”。如果匹配的结果是regex,则是正则导向引擎。如果结果是regex not,则是文本导向引擎。
正则导向的引擎总是返回最左边的匹配
这是非中重要的一点:即使在后边可能发现一个“更好”的匹配,正则导向的引擎也总是返回最左边的匹配。
当把<<cat>>应用到“He captured a catfish for his cat”,引擎先比较<<c>>和“H”,结果失败了。于是引擎再比较<<c>>和“e”,也失败了。直到第四个字符,<<c>>匹配了“c”。<<a>>匹配了第五个字符。到第六个字符<<t>>没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配。
5、字符类或者字符集
字符类(也称为字符集)是由一对方括号“[]”括起来的字符集合。使用字符集,正则引擎可以只匹配多个字符中的一个。如果你想匹配一个“a”或一个“e”,使用<<[ae]>>。使用<<gr[ae]y>>匹配gray或grey。在你不确定你要搜索的字符是采用美国英语还是英国英语时特别有用。一个字符集只匹配一个字符,<<gr[ae]y>>将不会匹配graay或graey。字符集中的字符顺序无关。
可以使用连字符“-”定义一个字符范围作为字符集。<<[0-9]>>匹配0到9之间的单个数字。可以使用不止一个范围。<<[0-9a-fA-F] >>匹配单个的十六进制数字,并且大小写不敏感。也可以结合范围定义与单个字符定义。<<[0-9a-fxA-FX]>>匹配一个十六进制数字或字母X。再次强调一下,字符和范围定义的先后顺序对结果没有影响。
字符集的一些应用
查找一个可能写错的单词,比如<<sep[ae]r[ae]te>> 或 <<li[cs]en[cs]e>>。
查找程序语言的标识符,<<A-Za-z_][A-Za-z_0-9]*>>。(*表示重复0或多次)
查找C风格的十六进制数<<0[xX][A-Fa-f0-9]+>>。(+表示重复一次或多次)
反字符集
在左方括号“[”后面紧跟一个尖括号“^”,将会对字符集取反。结果是字符集将匹配任何不在方括号中的字符。不像“.”,取反字符集是可以匹配回车换行符的。
需要记住的很重要的一点是,取反字符集必须要匹配一个字符。<<q[^u]>>并不意味着:匹配一个q,后面没有u跟着。它意味着:匹配一个q,后面跟着一个不是u的字符。所以它不会匹配“Iraq”中的q,而会匹配“Iraq is a country”中的q和一个空格符。事实上,空格符是匹配中的一部分,因为它是一个“不是u的字符”。
如果只想匹配一个q,条件是q后面有一个不是u的字符,可以用后面将讲到的向前查看来解决。
字符集中的元字符
需要注意的是,在字符集中只有4个 字符具有特殊含义。它们是:“] \ ^ -”。“]”代表字符集定义的结束;“\”代表转义;“^”代表取反;“-”代表范围定义。其他常见的元字符在字符集定义内部都是正常字符,不需要转义。例如,要搜索星号*或加号+,你可以用<<[+*]>>。当然,如果你对那些通常的元字符进行转义,你的正则表达式一样会工作得很好,但是这会降低可读性。
在字符集定义中为了将反斜杠“\”作为一个文字字符而非特殊含义的字符,你需要用另一个反斜杠对它进行转义。<<[\\x]>>将会匹配一个反斜杠和一个X。“]^-”都可以用反斜杠进行转义,或者将他们放在一个不可能使用到他们特殊含义的位置。我们推荐后者,因为这样可以增加可读性。比如对于字符“^”,将它放在除了左括号“[”后面的位置,使用的都是文字字符含义而非取反含义。如<<[x^]>>会匹配一个x或^。<<[]x]>>会匹配一个“]”或“x”。<<[-x]>>或<<[x-]>>都会匹配一个“-”或“x”。
字符集的简写
因为一些字符集非常常用,所以有一些简写方式。
<<\d>>代表<<[0-9]>>;
<<\w>>代表单词字符。这个是随正则表达式实现的不同而有些差异。绝大多数的正则表达式实现的单词字符集都包含了<<A-Za-z0-9_]>>。
<<\s>>代表“白字符”。这个也是和不同的实现有关的。在绝大多数的实现中,都包含了空格符和Tab符,以及回车换行符<<\r\n>>。
字符集的缩写形式可以用在方括号之内或之外。<<\s\d>>匹配一个白字符后面紧跟一个数字。<<[\s\d]>>匹配单个白字符或数字。<<[\da-fA-F]>>将匹配一个十六进制数字。
取反字符集的简写
<<[\S]>> = <<[^\s]>>
<<[\W]>> = <<[^\w]>>
<<[\D]>> = <<[^\d]>>
字符集的重复
如果用“?*+”操作符来重复一个字符集,将会重复整个字符集。而不仅是它匹配的那个字符。正则表达式<<[0-9]+>>会匹配837以及222。
如果仅仅想重复被匹配的那个字符,可以用向后引用达到目的。以后将讲到向后引用。
可以使用连字符“-”定义一个字符范围作为字符集。<<[0-9]>>匹配0到9之间的单个数字。可以使用不止一个范围。<<[0-9a-fA-F] >>匹配单个的十六进制数字,并且大小写不敏感。也可以结合范围定义与单个字符定义。<<[0-9a-fxA-FX]>>匹配一个十六进制数字或字母X。再次强调一下,字符和范围定义的先后顺序对结果没有影响。
字符集的一些应用
查找一个可能写错的单词,比如<<sep[ae]r[ae]te>> 或 <<li[cs]en[cs]e>>。
查找程序语言的标识符,<<A-Za-z_][A-Za-z_0-9]*>>。(*表示重复0或多次)
查找C风格的十六进制数<<0[xX][A-Fa-f0-9]+>>。(+表示重复一次或多次)
反字符集
在左方括号“[”后面紧跟一个尖括号“^”,将会对字符集取反。结果是字符集将匹配任何不在方括号中的字符。不像“.”,取反字符集是可以匹配回车换行符的。
需要记住的很重要的一点是,取反字符集必须要匹配一个字符。<<q[^u]>>并不意味着:匹配一个q,后面没有u跟着。它意味着:匹配一个q,后面跟着一个不是u的字符。所以它不会匹配“Iraq”中的q,而会匹配“Iraq is a country”中的q和一个空格符。事实上,空格符是匹配中的一部分,因为它是一个“不是u的字符”。
如果只想匹配一个q,条件是q后面有一个不是u的字符,可以用后面将讲到的向前查看来解决。
字符集中的元字符
需要注意的是,在字符集中只有4个 字符具有特殊含义。它们是:“] \ ^ -”。“]”代表字符集定义的结束;“\”代表转义;“^”代表取反;“-”代表范围定义。其他常见的元字符在字符集定义内部都是正常字符,不需要转义。例如,要搜索星号*或加号+,你可以用<<[+*]>>。当然,如果你对那些通常的元字符进行转义,你的正则表达式一样会工作得很好,但是这会降低可读性。
在字符集定义中为了将反斜杠“\”作为一个文字字符而非特殊含义的字符,你需要用另一个反斜杠对它进行转义。<<[\\x]>>将会匹配一个反斜杠和一个X。“]^-”都可以用反斜杠进行转义,或者将他们放在一个不可能使用到他们特殊含义的位置。我们推荐后者,因为这样可以增加可读性。比如对于字符“^”,将它放在除了左括号“[”后面的位置,使用的都是文字字符含义而非取反含义。如<<[x^]>>会匹配一个x或^。<<[]x]>>会匹配一个“]”或“x”。<<[-x]>>或<<[x-]>>都会匹配一个“-”或“x”。
字符集的简写
因为一些字符集非常常用,所以有一些简写方式。
<<\d>>代表<<[0-9]>>;
<<\w>>代表单词字符。这个是随正则表达式实现的不同而有些差异。绝大多数的正则表达式实现的单词字符集都包含了<<A-Za-z0-9_]>>。
<<\s>>代表“白字符”。这个也是和不同的实现有关的。在绝大多数的实现中,都包含了空格符和Tab符,以及回车换行符<<\r\n>>。
字符集的缩写形式可以用在方括号之内或之外。<<\s\d>>匹配一个白字符后面紧跟一个数字。<<[\s\d]>>匹配单个白字符或数字。<<[\da-fA-F]>>将匹配一个十六进制数字。
取反字符集的简写
<<[\S]>> = <<[^\s]>>
<<[\W]>> = <<[^\w]>>
<<[\D]>> = <<[^\d]>>
字符集的重复
如果用“?*+”操作符来重复一个字符集,将会重复整个字符集。而不仅是它匹配的那个字符。正则表达式<<[0-9]+>>会匹配837以及222。
如果仅仅想重复被匹配的那个字符,可以用向后引用达到目的。以后将讲到向后引用。
6、点号"."
使用“.”匹配几乎任意字符
在正则表达式中,“.”是最常用的元字符之一。不幸的是,它也容易被误用。
“.”匹配一个单个的字符而不管该字符是什么。唯一的例外是换行符。在本教程中谈到的引擎,缺省情况下都是不匹配换行符的。因此在缺省情况下,“.”等价于字符集[^\n\r](Window)或[^\n]( Unix)。
这个例外是有历史原因的,因为早期使用正则表达式的工具是基于行的,它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去,在这些工具中,字符串是不包含换行符的。因此“.”也就从不匹配换行符。
现代的工具和语言能够将正则表达式应用到很大的字符串甚至整个文件上去。在Perl中,“.”可以匹配换行符的模式被称作“单行模式”。很不幸,这是一个很容易混淆的名词。因为还有所谓“多行模式”。多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。
其他语言和正则表达式库也采用了Perl的术语定义。当在.NET Framework中使用正则表达式类时,你可以用类似下面的语句来激活单行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)
保守地使用点号“.”
点号可以说是最强大的元字符。它允许你偷懒:用一个点号,就能匹配几乎所有的字符。但是问题在于,它也常常会匹配不该匹配的字符。
以一个简单的例子来说明。如何匹配一个具有“mm/dd/yy”格式的日期,想允许用户来选择分隔符。很快能想到的一个方案是<<\d\d.\d\d.\d\d>>。看上去它能匹配日期“02/12/03”。问题在于02512703也会被认为是一个有效的日期。
<<\d\d[-/.]\d\d[-/.]\d\d>>看上去是一个好一点的解决方案。记住点号在一个字符集里不是元字符。这个方案远不够完善,它会匹配“99/99/99”。而<<[0-1]\d[-/.][0-3]\d[-/.]\d\d>>又更进一步。尽管他也会匹配“19/39/99”。你想要你的正则表达式达到如何完美的程度取决于你想达到什么样的目的。如果你想校验用户输入,则需要尽可能的完美。如果你只是想分析一个已知的源,并且我们知道没有错误的数据,用一个比较好的正则表达式来匹配你想要搜寻的字符就已经足够。
在正则表达式中,“.”是最常用的元字符之一。不幸的是,它也容易被误用。
“.”匹配一个单个的字符而不管该字符是什么。唯一的例外是换行符。在本教程中谈到的引擎,缺省情况下都是不匹配换行符的。因此在缺省情况下,“.”等价于字符集[^\n\r](Window)或[^\n]( Unix)。
这个例外是有历史原因的,因为早期使用正则表达式的工具是基于行的,它们都是一行一行的读入一个文件,将正则表达式分别应用到每一行上去,在这些工具中,字符串是不包含换行符的。因此“.”也就从不匹配换行符。
现代的工具和语言能够将正则表达式应用到很大的字符串甚至整个文件上去。在Perl中,“.”可以匹配换行符的模式被称作“单行模式”。很不幸,这是一个很容易混淆的名词。因为还有所谓“多行模式”。多行模式只影响行首行尾的锚定(anchor),而单行模式只影响“.”。
其他语言和正则表达式库也采用了Perl的术语定义。当在.NET Framework中使用正则表达式类时,你可以用类似下面的语句来激活单行模式:Regex.Match(“string”,”regex”,RegexOptions.SingleLine)
保守地使用点号“.”
点号可以说是最强大的元字符。它允许你偷懒:用一个点号,就能匹配几乎所有的字符。但是问题在于,它也常常会匹配不该匹配的字符。
以一个简单的例子来说明。如何匹配一个具有“mm/dd/yy”格式的日期,想允许用户来选择分隔符。很快能想到的一个方案是<<\d\d.\d\d.\d\d>>。看上去它能匹配日期“02/12/03”。问题在于02512703也会被认为是一个有效的日期。
<<\d\d[-/.]\d\d[-/.]\d\d>>看上去是一个好一点的解决方案。记住点号在一个字符集里不是元字符。这个方案远不够完善,它会匹配“99/99/99”。而<<[0-1]\d[-/.][0-3]\d[-/.]\d\d>>又更进一步。尽管他也会匹配“19/39/99”。你想要你的正则表达式达到如何完美的程度取决于你想达到什么样的目的。如果你想校验用户输入,则需要尽可能的完美。如果你只是想分析一个已知的源,并且我们知道没有错误的数据,用一个比较好的正则表达式来匹配你想要搜寻的字符就已经足够。
7、锚(anchor)
字符串开始和结束锚(anchor)
锚是一个特别的符号,它不匹配任何字符。相反,他们匹配的是字符之前或之后的位置。“^”匹配一行字符串第一个字符前的位置。<<^a>>将会匹配字符串“abc”中的a。<<^b>>将不会匹配“abc”中的任何字符。
类似的,$匹配字符串中最后一个字符后面的位置。所以<<c$>>匹配“abc”中的c。
应用场合
在编程语言中校验用户输入时,使用锚是非常重要的。如果你想校验用户的输入为整数,用<<^\d+$>>。
用户输入中,常常会有多余的前导空格或结束空格。你可以用<<^\s*>>和<<\s*$>>来匹配前导空格或结束空格。
使用“^”和“$”作为行的开始和结束锚
如果有一个多行的字符串。例如:“first line\r\nsecond line”(其中\r\n表示一个换行符)。常常需要逐行处理而不是处理整个字符串。因此,几乎所有的正则表达式引擎都提供一个选项,可以扩展这两种锚的含义。“^”可以匹配字串的开始位置(在f之前),以及每一个换行符的后面位置(在\r\n和s之间)。类似的,$会匹配字串的结束位置(最后一个e之后),以及每个新行符的前面(在e与\r\n之间)。
在.NET中,当你使用如下代码时,将会定义锚匹配每一个换行符的前面和后面位置:Regex.Match("string", "regex", RegexOptions.Multiline) ;string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--将会在每行的行首插入“> ”。
字符串绝对开始和结束锚(anchor)
<<\A>>只匹配整个字符串的开始位置,<<\Z>>只匹配整个字符串的结束位置。即使你使用了“多行模式”,<<\A>>和<<\Z>>也从不匹配新行符。
锚是一个特别的符号,它不匹配任何字符。相反,他们匹配的是字符之前或之后的位置。“^”匹配一行字符串第一个字符前的位置。<<^a>>将会匹配字符串“abc”中的a。<<^b>>将不会匹配“abc”中的任何字符。
类似的,$匹配字符串中最后一个字符后面的位置。所以<<c$>>匹配“abc”中的c。
应用场合
在编程语言中校验用户输入时,使用锚是非常重要的。如果你想校验用户的输入为整数,用<<^\d+$>>。
用户输入中,常常会有多余的前导空格或结束空格。你可以用<<^\s*>>和<<\s*$>>来匹配前导空格或结束空格。
使用“^”和“$”作为行的开始和结束锚
如果有一个多行的字符串。例如:“first line\r\nsecond line”(其中\r\n表示一个换行符)。常常需要逐行处理而不是处理整个字符串。因此,几乎所有的正则表达式引擎都提供一个选项,可以扩展这两种锚的含义。“^”可以匹配字串的开始位置(在f之前),以及每一个换行符的后面位置(在\r\n和s之间)。类似的,$会匹配字串的结束位置(最后一个e之后),以及每个新行符的前面(在e与\r\n之间)。
在.NET中,当你使用如下代码时,将会定义锚匹配每一个换行符的前面和后面位置:Regex.Match("string", "regex", RegexOptions.Multiline) ;string str = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline)--将会在每行的行首插入“> ”。
字符串绝对开始和结束锚(anchor)
<<\A>>只匹配整个字符串的开始位置,<<\Z>>只匹配整个字符串的结束位置。即使你使用了“多行模式”,<<\A>>和<<\Z>>也从不匹配新行符。
8 词界
元字符<<\b>>也是一种类似于<<^>>和<<$>>的“锚”,用来匹配单词边界位置,匹配长度0。
有4种不同位置常被鉴定为“单词边界”:
● 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”)
● 在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”)
● 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后
● 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面
“单词字符”是可以用<<\w>>匹配的字符,“非单词字符”是可以用<<\W>>匹配的字符。在大多数的正则表达式实现中,“单词字符”通常包括<<[a-zA-Z0-9_]>>。
例如:<<\b4\b>>能够匹配单个的4而不会匹配“44”中的4。可以说<<\b>>匹配一个“字母数字序列”的开始和结束的位置。
“单词边界”的反集为<<\B>>,他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。
有4种不同位置常被鉴定为“单词边界”:
● 在字符串的第一个字符前的位置(如果字符串的第一个字符是一个“单词字符”)
● 在字符串的最后一个字符后的位置(如果字符串的最后一个字符是一个“单词字符”)
● 在一个“单词字符”和“非单词字符”之间,其中“非单词字符”紧跟在“单词字符”之后
● 在一个“非单词字符”和“单词字符”之间,其中“单词字符”紧跟在“非单词字符”后面
“单词字符”是可以用<<\w>>匹配的字符,“非单词字符”是可以用<<\W>>匹配的字符。在大多数的正则表达式实现中,“单词字符”通常包括<<[a-zA-Z0-9_]>>。
例如:<<\b4\b>>能够匹配单个的4而不会匹配“44”中的4。可以说<<\b>>匹配一个“字母数字序列”的开始和结束的位置。
“单词边界”的反集为<<\B>>,他要匹配的位置是两个“单词字符”之间或者两个“非单词字符”之间的位置。
9、选择符
可以使用选择符<<|>>来匹配几个正则表达式中的一个。
如果要查找文本串“cat”或者“dog”,可以用<<cat|dog>>。如果想有更多的选择,只要扩展列表<<cat|dog|mouse|fish>>。
选择符在正则表达式中具有最低的优先级,也就是说,它告诉引擎要么匹配选择符左边的所有表达式,要么匹配右边的所有表达式。可以用圆括号来限制选择符的作用范围。如<<\b(cat|dog)\b>>,这样告诉正则引擎把(cat|dog)当成一个正则表达式单位来处理。
注意正则引擎的“Eager”性
正则引擎找到一个有效的匹配时,会停止搜索。因此在一定条件下,选择符两边的表达式的顺序对结果会有影响。假设用正则表达式搜索一个编程语言的函数列表:Get,GetValue,Set或SetValue。一个明显的解决方案是<<Get|GetValue|Set|SetValue>>。
当搜索SetValue时的结果,因为<<Get>>和<<GetValue>>都失败了,而<<Set>>匹配成功。因为正则导向的引擎的"Eager"性,所以它会返回第一个成功的匹配,就是“Set”,而不去继续搜索是否有其他更好的匹配。和期望的相反,正则表达式并没有匹配整个字符串。有几种可能的解决办法。一是考虑到正则引擎的“Eager”性,改变选项的顺序,例如使用<<GetValue|Get|SetValue|Set>>,这样可以优先搜索最长的匹配。也可以把四个选项结合起来成两个选项:<<Get(Value)?|Set(Value)?>>。因为问号重复符是贪婪的,所以SetValue总会在Set之前被匹配。
一个更好的方案是使用单词边界:<<\b(Get|GetValue|Set|SetValue)\b>>或<<\b(Get(Value)?|Set(Value)?\b>>。更进一步,既然所有的选择都有相同的结尾,可以把正则表达式优化为<<\b(Get|Set)(Value)?\b>>。
如果要查找文本串“cat”或者“dog”,可以用<<cat|dog>>。如果想有更多的选择,只要扩展列表<<cat|dog|mouse|fish>>。
选择符在正则表达式中具有最低的优先级,也就是说,它告诉引擎要么匹配选择符左边的所有表达式,要么匹配右边的所有表达式。可以用圆括号来限制选择符的作用范围。如<<\b(cat|dog)\b>>,这样告诉正则引擎把(cat|dog)当成一个正则表达式单位来处理。
注意正则引擎的“Eager”性
正则引擎找到一个有效的匹配时,会停止搜索。因此在一定条件下,选择符两边的表达式的顺序对结果会有影响。假设用正则表达式搜索一个编程语言的函数列表:Get,GetValue,Set或SetValue。一个明显的解决方案是<<Get|GetValue|Set|SetValue>>。
当搜索SetValue时的结果,因为<<Get>>和<<GetValue>>都失败了,而<<Set>>匹配成功。因为正则导向的引擎的"Eager"性,所以它会返回第一个成功的匹配,就是“Set”,而不去继续搜索是否有其他更好的匹配。和期望的相反,正则表达式并没有匹配整个字符串。有几种可能的解决办法。一是考虑到正则引擎的“Eager”性,改变选项的顺序,例如使用<<GetValue|Get|SetValue|Set>>,这样可以优先搜索最长的匹配。也可以把四个选项结合起来成两个选项:<<Get(Value)?|Set(Value)?>>。因为问号重复符是贪婪的,所以SetValue总会在Set之前被匹配。
一个更好的方案是使用单词边界:<<\b(Get|GetValue|Set|SetValue)\b>>或<<\b(Get(Value)?|Set(Value)?\b>>。更进一步,既然所有的选择都有相同的结尾,可以把正则表达式优化为<<\b(Get|Set)(Value)?\b>>。
10、正则表达式实战训练1
光说不练也不行,在讲理论的时候得配合实战训练,效果才会更好。下面给一个例子,用来学习基本的正则编程。
题目:要求取得字符串中的所有数字。
题目本身比较简单,我们可以采用常规的做法,从字符串的第一个字符开始,循环逐个判断每一个字符是数字还是非数字......
但常规做法显得比较麻烦,我们来看如何使用正则表达式来解决这个问题。
思路有二:
1. 采用查找的方式,匹配到数字就取出来,最后连成一个结果。 所用的正则表达式为 \d+,可以匹配一个或多个连续的数字。
2. 采用替换的方式,匹配到非数字就把它替换成空字符串。所用的正则表达式为 [^\d]+,可以匹配一个或多个连续的非数字。
究竟那种方式更好,大家自己判断一下就可以了,比较容易的。
程序代码及实际效果见附件:
正则表达式训练_01.rar
题目:要求取得字符串中的所有数字。
题目本身比较简单,我们可以采用常规的做法,从字符串的第一个字符开始,循环逐个判断每一个字符是数字还是非数字......
但常规做法显得比较麻烦,我们来看如何使用正则表达式来解决这个问题。
思路有二:
1. 采用查找的方式,匹配到数字就取出来,最后连成一个结果。 所用的正则表达式为 \d+,可以匹配一个或多个连续的数字。
2. 采用替换的方式,匹配到非数字就把它替换成空字符串。所用的正则表达式为 [^\d]+,可以匹配一个或多个连续的非数字。
究竟那种方式更好,大家自己判断一下就可以了,比较容易的。
程序代码及实际效果见附件:
正则表达式训练_01.rar
11、问号的使用
在正则表达式中,问号使它前面的符号变成可选项。如:<<colou?r>>可以匹配"colour"和"color"。
使用圆括号使一组字符成为可选项,问号在圆括号后。如:<<Nov(ember)?>>会匹配"Nov"和"November"。
在一个正则表达式中,可以使用多个问号,使多个符号成为可选项。如:<<Feb(ruary)? 23(rd)?>>匹配"February 23rd","February 23","Feb 23rd"和"Feb 23"。
在这里,要介绍正则表达式中的一个非常重要的概念------贪婪性。我们这里介绍的第一个贪婪的元字符就是问号。问号让正则引擎有两种选择:尽力去匹配问号前的部分;不匹配问号钱的部分。而正则引擎总是试图去匹配问号前的部分,只有当这样的尝试失败后,才会忽略掉问号前的部分,所以说问号是一个贪婪的元字符。
若用正则式<<Feb 23(rd)?>>去匹配字符串"Today is Feb 23rd, 2003",得到的结果总是"Feb 23rd"而不是"Feb 23",就是因为问号的贪婪性。
使用圆括号使一组字符成为可选项,问号在圆括号后。如:<<Nov(ember)?>>会匹配"Nov"和"November"。
在一个正则表达式中,可以使用多个问号,使多个符号成为可选项。如:<<Feb(ruary)? 23(rd)?>>匹配"February 23rd","February 23","Feb 23rd"和"Feb 23"。
在这里,要介绍正则表达式中的一个非常重要的概念------贪婪性。我们这里介绍的第一个贪婪的元字符就是问号。问号让正则引擎有两种选择:尽力去匹配问号前的部分;不匹配问号钱的部分。而正则引擎总是试图去匹配问号前的部分,只有当这样的尝试失败后,才会忽略掉问号前的部分,所以说问号是一个贪婪的元字符。
若用正则式<<Feb 23(rd)?>>去匹配字符串"Today is Feb 23rd, 2003",得到的结果总是"Feb 23rd"而不是"Feb 23",就是因为问号的贪婪性。
12、重复量词
使用*和+进行重复
11、中已经介绍过一种重复操作符或者说量词:问号?。它告诉正则引擎匹配前导字符0次或者1次,效果就是使它成为可选的。
+告诉引擎匹配前导字符1次或多次;*告诉引擎匹配前导字符0次或多次。
正则式 <[A-Za-z][A-Za-z0-9]*> 匹配没有属性的HTML标签,,“<”以及“>”是文字符号。第一个字符集[A-Za-z]匹配一个字母,第二个字符集[A-Za-z0-9]匹配一个字母或数字,星号重复第二个字符集一次或多次。因为我们使用星号,第二个字符集有0个匹配也是正确的,所以这个正则式可以匹配一个标签"<B>"。匹配字符串"<HTML>"时,第一个字符集匹配"H",星号将会导致第二个字符集重复匹配三次,匹配到"TML"。
有限重复
许多现在的正则引擎实现,允许定义对一个字符重复多少次。语法是:{min,max}。min和max都是非负整数。如果逗号有而max被忽略了,则max没有限制。如果逗号和max都被忽略了,则重复min次。
因此{0,}和*一样,{1,}和+ 的作用一样。
你可以用\b[1-9][0-9]{3}\b匹配1000~9999之间的数字(“\b”表示单词边界)。\b[1-9][0-9]{2,4}\b匹配一个在100~99999之间的数字。
注意贪婪性
假设用一个正则式去匹配一个HTML标签。知道输入将会是一个有效的HTML文件,因此正则式不需要排除那些无效的标签。所以如果是两个尖括号之间的内容,就应该是一个HTML标签。
很多正则式的新手首先会想到用正则式 <.+> ,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能期望会返回<EM>,然后继续进行匹配的时候,返回</EM>。但事实不会。正则式将会匹配“<EM>first</EM>”。显然这不是我们想要的结果。
原因在于“+”是贪婪的。也就是说,“+”会导致正则引擎试图尽可能地重复前导字符。只有当这种重复会引起整个正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,然后处理正则表达式余下的部分。
和“+”类似,“?”“*”的重复也是贪婪的。
11、中已经介绍过一种重复操作符或者说量词:问号?。它告诉正则引擎匹配前导字符0次或者1次,效果就是使它成为可选的。
+告诉引擎匹配前导字符1次或多次;*告诉引擎匹配前导字符0次或多次。
正则式 <[A-Za-z][A-Za-z0-9]*> 匹配没有属性的HTML标签,,“<”以及“>”是文字符号。第一个字符集[A-Za-z]匹配一个字母,第二个字符集[A-Za-z0-9]匹配一个字母或数字,星号重复第二个字符集一次或多次。因为我们使用星号,第二个字符集有0个匹配也是正确的,所以这个正则式可以匹配一个标签"<B>"。匹配字符串"<HTML>"时,第一个字符集匹配"H",星号将会导致第二个字符集重复匹配三次,匹配到"TML"。
有限重复
许多现在的正则引擎实现,允许定义对一个字符重复多少次。语法是:{min,max}。min和max都是非负整数。如果逗号有而max被忽略了,则max没有限制。如果逗号和max都被忽略了,则重复min次。
因此{0,}和*一样,{1,}和+ 的作用一样。
你可以用\b[1-9][0-9]{3}\b匹配1000~9999之间的数字(“\b”表示单词边界)。\b[1-9][0-9]{2,4}\b匹配一个在100~99999之间的数字。
注意贪婪性
假设用一个正则式去匹配一个HTML标签。知道输入将会是一个有效的HTML文件,因此正则式不需要排除那些无效的标签。所以如果是两个尖括号之间的内容,就应该是一个HTML标签。
很多正则式的新手首先会想到用正则式 <.+> ,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能期望会返回<EM>,然后继续进行匹配的时候,返回</EM>。但事实不会。正则式将会匹配“<EM>first</EM>”。显然这不是我们想要的结果。
原因在于“+”是贪婪的。也就是说,“+”会导致正则引擎试图尽可能地重复前导字符。只有当这种重复会引起整个正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,然后处理正则表达式余下的部分。
和“+”类似,“?”“*”的重复也是贪婪的。
14、分组与后向引用(Grouping and Backreference)
使用圆括号进行分组
把部分正则表达式置于一对圆括号内"()",可以将它们形成组。这样可以对整个组使用一些正则操作,例如重复操作。这里需要注意的是,只有圆括号才可以用于分组, 方括号和大括号都有不同的用途,不要混淆。
使用圆括号创建后向引用
“()”在分组的同时,也会创建一个“后向引用”,一个“后向引用”保存的是一个分组内正则表达式匹配的字符串。
在定义了一个正则表达式组的同时,“()”也创建了一个后向引用。一个后向引用保存了由"()"内的正则式匹配到的字符串。
除非你使用“非捕获”的“()”,否则该“()”会形成一个后向引用。这里记住后向引用会减慢正则引擎的速度,因为它需要完成一些额外的操作。如果你不使用后向引用,你可以使用非捕获的“()”来提高正则引擎的工作速度,但这样做的代价是会使你的正则式的易读性降低。
正则式"Set(Value)?"匹配"Set"或"SetValue"。在第一种情况中,第一个后向引用(这里也只有一个后向引用)是空的,因为它没有匹配任何字符串。在第二种情况中,第一个后向引用将会保存"Value"。
如果你不使用后向引用,你可以把这个正则式优化成"Set(?:Value)?",正则式中"("后的问号和冒号是一种特殊的语法格式,它会告诉正则引擎"()"不创建后向引用。注意这里的"("后的"?"和我们在11、中讲到的正则表达式中的问号是不一样的,因为"("本身是一个特殊符号,不是一个有效的正则符号。
如何使用后向引用
后向引用允许你重复使用后向引用中保存的内容。可以用“\数字”的方式进行引用。"\1"引用第一个匹配的后向引用组,"\2"引用第二个组,以此类推,"\n"引用第n个组。而"\0"则引用整个被匹配的正则表达式本身。
在正则式中使用后向引用
假设你想匹配一个HTML标签的开始标签和结束标签,以及标签中间的文本。比如<B>This is a test</B>,我们要匹配<B>和</B>以及中间的文字。我们可以用如下正则表达式:“<([A-Z][A-Z0-9]*)[^>]*>.*?</\1>”
首先,“<”将会匹配“<B>”的第一个字符“<”。然后[A-Z]匹配B,[A-Z0-9]*将会匹配0到多次字母数字,后面紧接着0到多个非“>”的字符。最后正则表达式的“>”将会匹配“<B>”的“>”。接下来正则引擎将对结束标签之前的字符进行惰性匹配,直到遇到一个“</”符号。然后正则表达式中的“\1”表示对前面匹配的组“([A-Z][A-Z0-9]*)”进行引用,在本例中,被引用的是标签名“B”。所以需要被匹配的结尾标签为“</B>”
你可以对相同的后向引用组进行多次引用,"([a-c])x\1x\1"将匹配“axaxa”、“bxbxb”以及“cxcxc”。如果用数字形式引用的组没有有效的匹配,则引用到的内容简单的为空。
一个后向引用不能用于它自身。"([abc]\1)"是错误的。因此你不能将"\0"用于一个正则表达式匹配本身,它只能用于替换操作中。
后向引用不能用于字符集内部。"(a)[\1b]"中的"\1"并不表示后向引用。在字符集内部,"\1"可以被解释为八进制形式的转码。
重复操作与后向引用
当对组使用重复操作符时,缓存里后向引用内容会被不断刷新,只保留最后匹配的内容。如果"()"中的正则式找到一个新的匹配,则后向引用中的内容会被覆盖。"([abc]+) "和"([abc])+"就有着明显的差别。对于字符串"abc",两个正则式都会匹配成功。第一个正则式将会把"abc"置于后向引用中,而第二个正则式仅会把"b"置于后向引种中。因为在第二个正则式中,"+"会导致"()"重复匹配3次,第一次"a"被保存,第二次"b"被保存,第三次"c"被保存,每一次,前一次的值都被覆盖。
这也意味着对于字符串"cab=cab", 正则式"([abc]+)=\1"会匹配成功,而"([abc])+=\1"则不能匹配成功。
后向引用使用实例
当编辑文字时,很容易就会输入重复单词,例如"the the"。使用"\b(\w+)\s+\1\b"可以检测到这些重复单词。要删除第二个单词,只要简单的利用替换功能,用"\1"替换就可以了。
把部分正则表达式置于一对圆括号内"()",可以将它们形成组。这样可以对整个组使用一些正则操作,例如重复操作。这里需要注意的是,只有圆括号才可以用于分组, 方括号和大括号都有不同的用途,不要混淆。
使用圆括号创建后向引用
“()”在分组的同时,也会创建一个“后向引用”,一个“后向引用”保存的是一个分组内正则表达式匹配的字符串。
在定义了一个正则表达式组的同时,“()”也创建了一个后向引用。一个后向引用保存了由"()"内的正则式匹配到的字符串。
除非你使用“非捕获”的“()”,否则该“()”会形成一个后向引用。这里记住后向引用会减慢正则引擎的速度,因为它需要完成一些额外的操作。如果你不使用后向引用,你可以使用非捕获的“()”来提高正则引擎的工作速度,但这样做的代价是会使你的正则式的易读性降低。
正则式"Set(Value)?"匹配"Set"或"SetValue"。在第一种情况中,第一个后向引用(这里也只有一个后向引用)是空的,因为它没有匹配任何字符串。在第二种情况中,第一个后向引用将会保存"Value"。
如果你不使用后向引用,你可以把这个正则式优化成"Set(?:Value)?",正则式中"("后的问号和冒号是一种特殊的语法格式,它会告诉正则引擎"()"不创建后向引用。注意这里的"("后的"?"和我们在11、中讲到的正则表达式中的问号是不一样的,因为"("本身是一个特殊符号,不是一个有效的正则符号。
如何使用后向引用
后向引用允许你重复使用后向引用中保存的内容。可以用“\数字”的方式进行引用。"\1"引用第一个匹配的后向引用组,"\2"引用第二个组,以此类推,"\n"引用第n个组。而"\0"则引用整个被匹配的正则表达式本身。
在正则式中使用后向引用
假设你想匹配一个HTML标签的开始标签和结束标签,以及标签中间的文本。比如<B>This is a test</B>,我们要匹配<B>和</B>以及中间的文字。我们可以用如下正则表达式:“<([A-Z][A-Z0-9]*)[^>]*>.*?</\1>”
首先,“<”将会匹配“<B>”的第一个字符“<”。然后[A-Z]匹配B,[A-Z0-9]*将会匹配0到多次字母数字,后面紧接着0到多个非“>”的字符。最后正则表达式的“>”将会匹配“<B>”的“>”。接下来正则引擎将对结束标签之前的字符进行惰性匹配,直到遇到一个“</”符号。然后正则表达式中的“\1”表示对前面匹配的组“([A-Z][A-Z0-9]*)”进行引用,在本例中,被引用的是标签名“B”。所以需要被匹配的结尾标签为“</B>”
你可以对相同的后向引用组进行多次引用,"([a-c])x\1x\1"将匹配“axaxa”、“bxbxb”以及“cxcxc”。如果用数字形式引用的组没有有效的匹配,则引用到的内容简单的为空。
一个后向引用不能用于它自身。"([abc]\1)"是错误的。因此你不能将"\0"用于一个正则表达式匹配本身,它只能用于替换操作中。
后向引用不能用于字符集内部。"(a)[\1b]"中的"\1"并不表示后向引用。在字符集内部,"\1"可以被解释为八进制形式的转码。
重复操作与后向引用
当对组使用重复操作符时,缓存里后向引用内容会被不断刷新,只保留最后匹配的内容。如果"()"中的正则式找到一个新的匹配,则后向引用中的内容会被覆盖。"([abc]+) "和"([abc])+"就有着明显的差别。对于字符串"abc",两个正则式都会匹配成功。第一个正则式将会把"abc"置于后向引用中,而第二个正则式仅会把"b"置于后向引种中。因为在第二个正则式中,"+"会导致"()"重复匹配3次,第一次"a"被保存,第二次"b"被保存,第三次"c"被保存,每一次,前一次的值都被覆盖。
这也意味着对于字符串"cab=cab", 正则式"([abc]+)=\1"会匹配成功,而"([abc])+=\1"则不能匹配成功。
后向引用使用实例
当编辑文字时,很容易就会输入重复单词,例如"the the"。使用"\b(\w+)\s+\1\b"可以检测到这些重复单词。要删除第二个单词,只要简单的利用替换功能,用"\1"替换就可以了。
15、正则表达式实战训练3
问题: 字符串中1个或者连续多个空格替换成一个Tab键
分析: 由于字符串中连续的空格的个数不太确定,如果我们逐个字符去分析的话,也是比较麻烦的,但如果用正则去实现,则简单的多。这也是一个典型的适合用正则解决的问题。解决方式如下:
Sub test()
Dim RegEx As Object
Set RegEx = CreateObject("vbscript.regexp")
With RegEx
.Global = True
.Pattern = "\s+"
End With
Range("B1").Value = RegEx.Replace(Range("A1").Text, Chr(9))
Set RegEx = Nothing
End Sub
分析: 由于字符串中连续的空格的个数不太确定,如果我们逐个字符去分析的话,也是比较麻烦的,但如果用正则去实现,则简单的多。这也是一个典型的适合用正则解决的问题。解决方式如下:
Sub test()
Dim RegEx As Object
Set RegEx = CreateObject("vbscript.regexp")
With RegEx
.Global = True
.Pattern = "\s+"
End With
Range("B1").Value = RegEx.Replace(Range("A1").Text, Chr(9))
Set RegEx = Nothing
End Sub
16、正则表达式实战训练4
问题:查找字符串中连续9个数字字符并显示其开始位置。
Sub test()
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True
objRegEx.Pattern = "\d{9}"
strSearchString = "aaaaaaa123456789qqqqqqqqqqqq234567891aaaa12345678"
Set colMatches = objRegEx.Execute(strSearchString)
If colMatches.Count > 0 Then
strMessage = "The following user accounts were found:" & vbCrLf
For Each strMatch In colMatches
strMessage = strMessage & strMatch.Value & " (character position " & _
strMatch.FirstIndex & ")" & vbCrLf
Next
End If
MsgBox strMessage
End Sub
Sub test()
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True
objRegEx.Pattern = "\d{9}"
strSearchString = "aaaaaaa123456789qqqqqqqqqqqq234567891aaaa12345678"
Set colMatches = objRegEx.Execute(strSearchString)
If colMatches.Count > 0 Then
strMessage = "The following user accounts were found:" & vbCrLf
For Each strMatch In colMatches
strMessage = strMessage & strMatch.Value & " (character position " & _
strMatch.FirstIndex & ")" & vbCrLf
Next
End If
MsgBox strMessage
End Sub
17、实用的正则查找封装函数
这是一个封装的查找函数,使用正则实现查找功能,函数实现如下,使用时,把下面的函数置于VBA工程的普通模块中,就可以在代码中引用或者在单元格中直接使用函数
Function RegExpFind(LookIn As String, PatternStr As String, Optional Pos, Optional MatchCase As Boolean = True)
' This function uses Regular Expressions to parse a string (LookIn), and return matches to a
' pattern (PatternStr). Use Pos to indicate which match you want:
' Pos omitted : function returns a zero-based array of all matches
' Pos = 0 : the last match
' Pos = 1 : the first match
' Pos = 2 : the second match
' Pos = <positive integer> : the Nth match
' If Pos is greater than the number of matches, is negative, or is non-numeric, the function
' returns an empty string. If no match is found, the function returns an empty string
' If MatchCase is omitted or True (default for RegExp) then the Pattern must match case (and
' thus you may have to use [a-zA-Z] instead of just [a-z] or [A-Z]).
' If you use this function in Excel, you can use range references for any of the arguments.
' If you use this in Excel and return the full array, make sure to set up the formula as an
' array formula. If you need the array formula to go down a column, use TRANSPOSE()
Dim RegX As Object
Dim TheMatches As Object
Dim Answer() As String
Dim Counter As Long
' Evaluate Pos. If it is there, it must be numeric and converted to Long
If Not IsMissing(Pos) Then
If Not IsNumeric(Pos) Then
RegExpFind = ""
Exit Function
Else
Pos = CLng(Pos)
End If
End If
' Create instance of RegExp object
Set RegX = CreateObject("VBScript.RegExp")
With RegX
.Pattern = PatternStr
.Global = True
.IgnoreCase = Not MatchCase
End With
' Test to see if there are any matches
If RegX.test(LookIn) Then
' Run RegExp to get the matches, which are returned as a zero-based collection
Set TheMatches = RegX.Execute(LookIn)
' If Pos is missing, user wants array of all matches. Build it and assign it as the
' function's return value
If IsMissing(Pos) Then
ReDim Answer(0 To TheMatches.Count - 1) As String
For Counter = 0 To UBound(Answer)
Answer(Counter) = TheMatches(Counter)
Next
RegExpFind = Answer
' User wanted the Nth match (or last match, if Pos = 0). Get the Nth value, if possible
Else
Select Case Pos
Case 0 ' Last match
RegExpFind = TheMatches(TheMatches.Count - 1)
Case 1 To TheMatches.Count ' Nth match
RegExpFind = TheMatches(Pos - 1)
Case Else ' Invalid item number
RegExpFind = ""
End Select
End If
' If there are no matches, return empty string
Else
RegExpFind = ""
End If
' Release object variables
Set RegX = Nothing
Set TheMatches = Nothing
End Function
Function RegExpFind(LookIn As String, PatternStr As String, Optional Pos, Optional MatchCase As Boolean = True)
' This function uses Regular Expressions to parse a string (LookIn), and return matches to a
' pattern (PatternStr). Use Pos to indicate which match you want:
' Pos omitted : function returns a zero-based array of all matches
' Pos = 0 : the last match
' Pos = 1 : the first match
' Pos = 2 : the second match
' Pos = <positive integer> : the Nth match
' If Pos is greater than the number of matches, is negative, or is non-numeric, the function
' returns an empty string. If no match is found, the function returns an empty string
' If MatchCase is omitted or True (default for RegExp) then the Pattern must match case (and
' thus you may have to use [a-zA-Z] instead of just [a-z] or [A-Z]).
' If you use this function in Excel, you can use range references for any of the arguments.
' If you use this in Excel and return the full array, make sure to set up the formula as an
' array formula. If you need the array formula to go down a column, use TRANSPOSE()
Dim RegX As Object
Dim TheMatches As Object
Dim Answer() As String
Dim Counter As Long
' Evaluate Pos. If it is there, it must be numeric and converted to Long
If Not IsMissing(Pos) Then
If Not IsNumeric(Pos) Then
RegExpFind = ""
Exit Function
Else
Pos = CLng(Pos)
End If
End If
' Create instance of RegExp object
Set RegX = CreateObject("VBScript.RegExp")
With RegX
.Pattern = PatternStr
.Global = True
.IgnoreCase = Not MatchCase
End With
' Test to see if there are any matches
If RegX.test(LookIn) Then
' Run RegExp to get the matches, which are returned as a zero-based collection
Set TheMatches = RegX.Execute(LookIn)
' If Pos is missing, user wants array of all matches. Build it and assign it as the
' function's return value
If IsMissing(Pos) Then
ReDim Answer(0 To TheMatches.Count - 1) As String
For Counter = 0 To UBound(Answer)
Answer(Counter) = TheMatches(Counter)
Next
RegExpFind = Answer
' User wanted the Nth match (or last match, if Pos = 0). Get the Nth value, if possible
Else
Select Case Pos
Case 0 ' Last match
RegExpFind = TheMatches(TheMatches.Count - 1)
Case 1 To TheMatches.Count ' Nth match
RegExpFind = TheMatches(Pos - 1)
Case Else ' Invalid item number
RegExpFind = ""
End Select
End If
' If there are no matches, return empty string
Else
RegExpFind = ""
End If
' Release object variables
Set RegX = Nothing
Set TheMatches = Nothing
End Function
18、实用的正则替换封装函数
这是一个封装的替换函数,使用正则实现替换功能,函数实现如下,使用时,把下面的函数置于VBA工程的普通模块中,就可以在代码中引用或者在单元格中直接使用函数
Function RegExpReplace(LookIn As String, PatternStr As String, Optional ReplaceWith As String = "", Optional ReplaceAll As Boolean = True, Optional MatchCase As Boolean = True)
' This function uses Regular Expressions to parse a string, and replace parts of the string
' matching the specified pattern with another string. The optional argument ReplaceAll controls
' whether all instances of the matched string are replaced (True) or just the first instance (False)
' By default, RegExp is case-sensitive in pattern-matching. To keep this, omit MatchCase or
' set it to True
' If you use this function from Excel, you may substitute range references for all the arguments
Dim RegX As Object
Set RegX = CreateObject("VBScript.RegExp")
With RegX
.Pattern = PatternStr
.Global = ReplaceAll
.IgnoreCase = Not MatchCase
End With
RegExpReplace = RegX.Replace(LookIn, ReplaceWith)
Set RegX = Nothing
End Function
Function RegExpReplace(LookIn As String, PatternStr As String, Optional ReplaceWith As String = "", Optional ReplaceAll As Boolean = True, Optional MatchCase As Boolean = True)
' This function uses Regular Expressions to parse a string, and replace parts of the string
' matching the specified pattern with another string. The optional argument ReplaceAll controls
' whether all instances of the matched string are replaced (True) or just the first instance (False)
' By default, RegExp is case-sensitive in pattern-matching. To keep this, omit MatchCase or
' set it to True
' If you use this function from Excel, you may substitute range references for all the arguments
Dim RegX As Object
Set RegX = CreateObject("VBScript.RegExp")
With RegX
.Pattern = PatternStr
.Global = ReplaceAll
.IgnoreCase = Not MatchCase
End With
RegExpReplace = RegX.Replace(LookIn, ReplaceWith)
Set RegX = Nothing
End Function
19、正则表达式实战训练5
问题:判断一个邮件地址格式是否有效
Private Sub CommandButton1_Click()
If IsValidEmail(TextBox1) Then 'Email address is stored in TextBox1
MsgBox "valid email address."
Else
MsgBox "Not a valid email address."
End If
End Sub
Private Function IsValidEmail(value As String) As Boolean
Dim RE As Object
Set RE = CreateObject("vbscript.RegExp")
RE.Pattern = "^[a-zA-Z0-9\._-]+@([a-zA-Z0-9_-]+\.)+([a-zA-Z]{2,3})$"
IsValidEmail = RE.Test(value)
Set RE = Nothing
End Function
Private Sub CommandButton1_Click()
If IsValidEmail(TextBox1) Then 'Email address is stored in TextBox1
MsgBox "valid email address."
Else
MsgBox "Not a valid email address."
End If
End Sub
Private Function IsValidEmail(value As String) As Boolean
Dim RE As Object
Set RE = CreateObject("vbscript.RegExp")
RE.Pattern = "^[a-zA-Z0-9\._-]+@([a-zA-Z0-9_-]+\.)+([a-zA-Z]{2,3})$"
IsValidEmail = RE.Test(value)
Set RE = Nothing
End Function