正则表达式学习
正则表达式简单来讲就是用来筛选文本数据用的公式。几乎所有的程序设计语言都会支持正则表达式检索,而软件都是由程序编写的,因此只要程序作者原意,都是可以让做出的软件支持正则表达式的。如果你看到某款软件说明可以用正则表达式,那这篇文章就有用了。
1.0最简单的正则表达式
假设你要搜索一个包含字符"cat"的字符串,搜索用的正则表达式就是"cat"。如果搜索对大小写不敏感,单词"catalog"、"Catherine"、"sophisticated"都可以匹配。
1.1 句点符号
假设你在玩英文拼字游戏,想要找出三个字母的单词,而且这些单词必须以"t"字母开头,以"n"字母结束。另外,假设有一本英文字典,你可以用正则表达式搜索它的全部内容。要构造出这个正则表达式,你可以使用一个通配符——句点符号"."——来表达任一单个字符。这样,完整的表达式就是"t.n",它匹配"tan"、"ten"、"tin"和"ton",还匹配"t#n"、"t_n"甚至"t n",还有其他许多无意义的组合。这是因为句点符号匹配所有字符,包括空格、Tab字符甚至换行符。
1.2 方括号符号
为了解决句点符号匹配范围过于广泛这一问题,你可以在方括号"[]"里面指定看来有意义的单个字符。此时,只有方括号里面指定的字符才参与匹配。也就是说,正则表达式"t[aeio]n"只匹配"tan"、"Ten"、"tin"和"ton"。但"Toon"不匹配,因为在方括号之内你只能匹配单个字符,"Tun"也不匹配,因为方括号中没有包含"u"这个字符。
1.3 "或"符号
如果除了上面匹配的所有单词之外,你还想要匹配"Toon",那么,你可以使用"|"操作符。"|"操作符的基本意义就是“或”运算。要匹配"toon",使用"t(a|e|i|o|oo)n"正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符,这里必须使用圆括号"()"。圆括号还可以用来分组,具体请参见后面1.7.4介绍。
1.4 表示匹配次数的限定符
表一显示了表示匹配次数的限定符,这些符号用来确定紧靠该符号左边的符号出现的次数:
符号 | 重复次数 |
---|---|
* | 0次或更多次 |
+ | 1次或更多次 |
? | 0次或1次 |
{n} | 恰好n次 |
{n,} | n次或更多次 |
{n,m} | n到m次 |
(注:某些程序语言中可能对"+"不支持。)
假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中,连字符"-"有着特殊的意义,它表示一个范围,比如从0到9。因此,匹配社会安全号码中的连字符号时,它的前面要加上一个转义字符"\"。
图一:匹配所有123-12-1234形式的社会安全号码
假设进行搜索的时候,你希望连字符号可以出现,也可以不出现,即999-99-9999和999999999都属于正确的格式。这时,你可以在连字符号后面加上"?"数量限定符号,如图二所示:
图二:匹配所有123-12-1234和123121234形式的社会安全号码
下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分"[0-9]{4}",再加上字母部分"[A-Z]{2}"。图三显示了完整的正则表达式。
图三:匹配典型的美国汽车牌照号码,如8836KV
1.5 "否"符号
"^"符号用在方括号中称为"否"符号,表示不想要匹配的字符。例如,图四的正则表达式匹配所有单词,但以"X"字母开头的单词除外。如果不在方括号中为另外意思,详见后面1.7.2介绍。
图四:匹配所有单词,但“X”开头的除外
1.6 圆括号和空白符号
假设要从格式为"June 26, 1951"的生日日期中提取出月份部分,用来匹配该日期的正则表达式可以如图五所示:
图五:匹配所有Moth DD,YYYY格式的日期
新出现的"\s"符号是空白符号,匹配所有的空白字符,包括Tab字符。如果字符串正确匹配,接下来如何提取出月份部分呢?只需在月份周围加上一个圆括号创建一个组,然后用程序语言就能从分组中提取出它的值。修改后的正则表达式如图六所示:
图六:匹配所有Month DD,YYYY格式的日期,定义月份值为第一个组
1.7 其它符号
表二是其它一些重要的符号(包括之前介绍和未介绍的):
表二:正则表达式符号含义 | ||
符号 | 含义 | 近似或等价的正则表达式 |
[x] | 匹配x单个字符 | |
[xyz] | 匹配xyz这三个字母中的一个 | |
[^x] | 匹配除了x以外的任意单个字符 | |
[^xyz] | 匹配除了xyz这三个字母以外的任意单个字符 | |
\d | 匹配单个数字 | [0-9] |
\D | 匹配单个非数字 | [^0-9] |
\w | 匹配单个字母、数字、下划线或汉字 | [a-z0-9] |
\W | 匹配单个不是字母、数字、下划线或汉字 | [^a-z0-9] |
\s | 匹配单个空白符(包括空格,制表符(Tab),换行符,中文全角空格等) | [\t\n\r\f] |
\S | 匹配单个非空白符 | [^\t\n\r\f] |
\b | 匹配单词的开始或结束 | |
[\b] | 在方括号中为退格符 | \u0008 |
^ | 匹配字符串的开始或行首(在方括号外) | |
$ | 匹配字符串的结束或行尾(在方括号外) | |
. | 匹配任意单个字符(有时不包括换行符"\n") | |
\. | 句点本身 | |
\* | 星号本身 | \x2A |
\\ | 反斜杠本身 | |
\a | 响铃报警字符(打印它的效果是电脑嘀一声) | \u0007 |
\t | 制表符,Tab | \u0009 |
\r | 回车 | \u000D |
\v | 竖向制表符 | \u000B |
\f | 换页符 | \u000C |
\n | 换行符 | \u000A |
\e | Escape | \u001B |
\0nn | ASCII代码中八进制代码为nn的字符 | |
\xnn | ASCII代码中十六进制代码为nn的字符 | |
\unnnn | Unicode代码中十六进制代码为nnnn的字符 | |
\cN | ASCII控制字符。比如\cC代表Ctrl+C | |
\A | 字符串开头(类似^,但不受处理多行选项的影响) | |
\Z | 字符串结尾(类似$,但不受处理多行选项的影响) | |
\G | 当前搜索的开头 | |
\p{name} | Unicode中命名为name的字符类,例如\p{IsGreek} | |
* | 对前面的符号重复0次或更多次 | |
+ | 对前面的符号重复1次或更多次(有时不支持) | |
? | 对前面的符号重复0次或1次 | |
{n} | 对前面的符号恰好重复n次 | |
{n,} | 对前面的符号重复n次或更多次 | |
{n,m} | 对前面的符号重复n次到m次 | |
*? | 对前面的符号重复任意次,但尽可能少重复 | |
+? | 对前面的符号重复1次或更多次,但尽可能少重复 | |
?? | 对前面的符号重复0次或1次,但尽可能少重复 | |
{n,}? | 对前面的符号重复n次以上,但尽可能少重复 | |
{n,m}? | 对前面的符号重复n到m次,但尽可能少重复 | |
(exp) | 自动命名的分组,exp为子正则表达式(同下),一般按顺序命名为1~9 | |
(?<name>exp) | 以name命名的分组,也可以写成(?'name'exp) | |
(?:exp) | 无命名的分组,无法被引用 | |
(?=exp) | 匹配exp前面的内容,不包括exp | |
(?<=exp) | 匹配exp后面的内容,不包括exp | |
(?!exp) | 匹配后面跟的不是exp的内容 | |
(?<!exp) | 匹配前面不是exp的内容 | |
(?#comment) | 注释作用 |
下面是对上表的一些说明和举例:
1.7.0"\d"
前面1.4中社会安全号码的例子,所有出现"[0-9]"的地方我们可以使用"\d"代替。修改后的正则表达式如图七所示:
图七:匹配所有123-12-1234格式的社会安全号码
另一个IP地址的匹配例子,图八:
图八:匹配IP地址
1.7.1"\b"
表示匹配单词的开始或结束。
\ba\w*\b匹配以字母a开头的单词;\b\w{6}\b 匹配刚好6个字符的单词。
但如在"[]"内为退格符。
1.7.2"^"和"$"
表示匹配字符串开始和结束,同"\b"差不多。
^\d{5,12}$匹配5位到12位数字。
另外可以通过程序命令使其变成行首行尾匹配。
"^"符号用在方括号中称为"否"符号,详见之前1.5介绍。
1.7.3"\"转义符
当需要匹配" . $ ^ { [ ( | ) * + ? \"这些字符本身时,前面需要加转义符"\",其它字符不用。但一般情况下,为了记忆方便"] } /"之前也会加上转义符。如图九:
图九:匹配以"["开头和"]"结束的字符
1.7.4"()"小括号
小括号用来指定子表达式(也叫做分组)。"()" 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一般可以保存9个分组),它们可以用 \1 到\9 的符号来引用。
(\d{1,3}\.){3}\d{1,3}匹配如999.999.999.999这种格式,可以用来匹配IP地址,这个和1.7.0图八的例子是等效的。
默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。然后可用 \1 到\9 的符号来引用,注意引用的是以匹配好的分组内容。
\b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者hello hello。这个表达式首先是匹配一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字"\b(\w+)\b",这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符"\s+",最后是分组1中捕获的内容(也就是前面匹配的那个单词)"\1"。
1.7.5自定义分组名称
你也可以自己指定子表达式的组名。要指定一个子表达式的组名,请使用这样的语法:(?<name>exp),其中尖括号可换成单引号(?'name'exp),exp为子表达式,可以使用\k<name>来引用。1.7.5的例子也可以写成:\b(?<Word>\w+)\b\s+\k<Word>\b,等效于\b(\w+)\b\s+\1\b。
1.7.5零宽断言(?=exp)(?<=exp)(?!exp)(?<!exp)
接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。最好还是拿例子来说明吧:
断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。
(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。
(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890。
下面这个例子同时使用了这两种断言:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
(?!exp)零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
同理,我们可以用(?<!exp)零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
一个更能表现零宽断言的真正用途的例子:(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内里的内容。(<?(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的"\/",使用了转义符,这个也可以不转义直接用"/";"\1"则是一个引用,引用了1号分组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括<b>和</b>本身)。
1.7.6贪婪和懒惰匹配
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面1.4中给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab两个。
1.8处理选项
表三:为一些常用的处理选项,可在程序设计语言中实现
名称 | 说明 |
---|---|
IgnoreCase(忽略大小写) | 匹配时不区分大小写。 |
Multiline(多行模式) | 更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
Singleline(单行模式) | 更改.的含义,使它与每一个字符匹配(包括换行符\n)。 |
IgnorePatternWhitespace(忽略空白) | 忽略表达式中的非转义空白并启用由#标记的注释。 |
ExplicitCapture(显式捕获) | 仅捕获已被显式命名的组。 |
以C#语言为例,可以使用Regex(String, RegexOptions)构造函数来设置正则表达式的处理选项。如:Regex regex = new Regex(@"\ba\w{6}\b", RegexOptions.IgnoreCase);
1.9更多的学习
更多的有关正则表达式的学习可以去参考微软的开发人员指南http://msdn.microsoft.com/zh-cn/library/az24scfc(v=VS.90).aspx。
1.10更多的例子
可以参考百度百科http://baike.baidu.com/view/94238.htm?fr=ala0_1。
1.11本文内容参考摘要
天极网《Java正则表达式详解》,2005年;
deerchao《正则表达式30分钟入门教程》,2009年。
最新评论