正则表达式

文本格式约定: 专业术语  元字符/语法格式  正则表达式  正则表达式中的一部分(用于分析)  用于在其中搜索的字符串  对正则表达式或其中一部分的说明

学习笔记:

正则表达式究竟是什么?

在编写处理字符串的程序或网页时,常常会有查找符合某些复杂规则的字符串的须要。 正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。
就像使用Windows/Dos下查找的 通配符(wildcard),也就是 *?。若是你想查找某个目录下的全部的Word文档的话,你会搜索 *.doc。在这里, *会被解释成任意的字符串。
和通配符相似,正则表达式也是用来进行 文本 匹配的工具,只不过比起通配符,它能更精确地描述你的需求.
  正则表达式是用于进行文本匹配的工具,在给定的字符串中,寻找与给定的正则表达式相匹配的部分。 匹配在本文里可能会有三种意思:一种是一个字符串匹配一个表达式;一种是在字符串里匹配正则表达式;还有一种是字符串中知足给定的正则表达式的一部分_至关于不彻底匹配.

例子:

假设你在一篇英文小说里查找 hi,你可使用正则正则表达式 hi
提示它能够精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。一般,处理正则表达式的工具会提供一个忽略大小写的选项,若是选中了这个选项,它能够匹配hi,HI,Hi,hI这四种状况中的任意一种。
  但不少单词里包含hi这两个连续的字符,好比him,history,high等等。若是要精确地查找hi这个单词的话,就应该使用\bhi\b
\b是正则表达式规定的一个特殊代码(某些人叫它元字符,metacharacter),表明着单词的开头或结尾,也就是单词的分界处虽然一般英文的单词是由空格或标点符号或换行来分隔的,可是 \b并不匹配这些单词分隔符中的任何一个, 只匹配一个位置
再者, 若是要找的是hi后面不远处跟着一个Lucy,应该用\bhi\b.*\bLucy\b
这里, .是另外一个元字符,匹配 除了换行符之外的任意字符*一样是元字符,不过它表明的不是字符,也不是位置,而是数量——它指定* 前边的内容能够连续重复出现任意次以使整个表达式获得匹配。所以, .*连在一块儿就意味着 任意数量的不包含换行的字符。如今 \bhi\b.*\bLucy\b的意思就很明显了: 先是一个单词hi,而后是任意个任意字符(但不能是换行),最后是Lucy这个单词
若是同时使用其它的一些元字符,咱们就能构造出功能更强大的正则表达式。
例二:
0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串: 以0开头,而后是两个数字,而后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。固然,这个例子只能匹配区号为3位的情形)。
(注:这里的\d是一个新的元字符,匹配任意的数字(0,或1,或2,或……)-不是元字符,只匹配它自己——连字号。)
为了不重复,咱们也能够这样写这个表达式:0\d{2}-\d{8}。 这里\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)

测试正则表达式

首先在确保已经安装了 .Net Framework 2.0,而后 下载Regex Tester。这是个绿色软件,下载完后打开压缩包,直接运行RegexTester.exe就能够了。
下面是Regex Tester运行时的截图:
Regex

元字符

元字符,如 \b,.,*还有 \d.固然还有更多的元字符可用,好比 \s匹配 任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等\w 匹配 字母或数字或下划线或汉字
例子:
\ba\w*\b匹配 以字母a开头的单词——先是某个单词开始处(\b),而后是字母a,而后是任意数量的字母或数字(\w*),最后是单词结束处(\b)
\d+匹配 1个或更多连续的数字。这里的 +是和 *相似的元字符, 不一样的是*匹配重复任意次(多是0次),而+则匹配重复1次或更屡次
\b\w{6}\b 匹配 恰好6个字母/数字的单词
表1.经常使用的元字符 代码说明
. 匹配除换行符之外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
元字符 ^(和数字6在同一个键位上的符号)以及 $\b有点相似,都匹配一个位置。 ^匹配你要用来查找的字符串的开头, $匹配结尾。这两个代码在验证输入的内容时很是有用,好比一个网站若是要求填写的QQ号必须为5位到12位数字时,可使用: ^\d{5,12}$
这里的 {5,12}和前面介绍过的 {2}是相似的,只不过 {2}匹配只能很少很多重复2次{5,12}则是 重复的次数不能少于5次,不能多于12次,不然都不匹配。
由于使用了 ^$,因此输入的整个字符串都要用来和 \d{5,12}来匹配,也就是说整个输入 必须是5到12个数字,所以若是输入的QQ号能匹配这个正则表达式的话,那就符合要求了。
和忽略大小写的选项相似,有些正则表达式处理工具还有一个处理多行的选项。若是选中了这个选项, ^$的意义就变成了 匹配行的开始处和结束处

字符转义

若是你想查找元字符自己的话,好比你查找 .,或者 *,就出现了问题:你无法指定它们,由于它们会被解释成其它的意思。这时你就必须使用 \来取消这些字符的特殊意义。所以,你应该使用 \.\*固然,要查找 \自己,你也得用 \\.
例如www\.unibetter\.com匹配 [url]www.unibetter.com[/url]c:\\Windows匹配 c:\Windows

重复

表2.经常使用的限定符 代码/语法说明
* 重复零次或更屡次
+ 重复一次或更屡次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更屡次
{n,m} 重复n到m次
重复的例子:
Windows\d+匹配 Windows后面跟1个或更多数字
13\d{9}匹配 13后面跟9个数字(中国的手机号)
^\w+匹配 一行的第一个单词(或整个字符串的第一个单词,具体匹配哪一个意思得看选项设置)

字符类

要想查找数字,字母或数字,空白是很简单的,由于已经有了对应这些字符集合的元字符,可是若是你想匹配没有预约义元字符的字符集合(好比元音字母a,e,i,o,u),应该怎么办?
你只须要在中括号里列出它们就好了,像 [aeiou]就匹配 任何一个英文元音字母[.?!]匹配 标点符号(.或?或!)(英文语句一般只以这三个标点结束)。
也能够轻松地指定一个字符 范围,像 [0-9]表明的含意与 \d就是彻底一致的: 一位数字,同理 [a-z0-9A-Z_]也彻底等同于 \w(若是只考虑英文的话)。
下面是一个更复杂的表达式: \(?0\d{2}[) -]?\d{8}
这个表达式能够匹配 几种格式的电话号码,像 (010)88886666,或 022-22334455,或 02912345678等。
分析:首先是一个转义字符 \(,它能出现0次或1次( ?),而后是一个 0,后面跟着2个数字( \d{2}),而后是 )-空格中的一个,它出现1次或不出现( ?),最后是8个数字( \d{8})。不幸的是,它也能匹配 010)12345678(022-87654321这样的“不正确”的格式。

反义

有时须要查找不属于某个能简单定义的字符类的字符。好比想查找除了数字之外,其它任意字符都行的状况,这时须要用到 反义
表3.经常使用的反义代码 代码/语法说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x之外的任意字符
[^aeiou] 匹配除了aeiou这几个字母之外的任意字符
例子: \S+匹配 不包含空白符的字符串
<a[^>]+>匹配 用尖括号括起来的以a开头的字符串

替换

正则表达式里的 替换指的是有几种规则,若是知足其中任意一种规则都应该当成匹配,具体方法是用 |把不一样的规则分隔开。
例子:
0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能 匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)
\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式 匹配3位区号的电话号码,其中区号能够用小括号括起来,也能够不用,区号与本地号间能够用连字号或空格间隔,也能够没有间隔。能够试试用替换|把这个表达式扩展成也支持4位区号的。
\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之因此要给出这个例子是由于它能说明一个问题: 使用替换时,顺序是很重要的。若是你把它改为 \d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。缘由是匹配替换时,将会从左到右地测试每一个分枝条件,若是知足了某个分枝的话,就不会去管其它的替换条件了。
Windows98|Windows2000|WindosXP这个例子是为了告诉你替换不只仅能用于两种规则,也能用于更多种规则。

分组

咱们已经提到了怎么重复单个字符(直接在字符后面加上限定符就好了);但若是想要重复多个字符又该怎么办?你能够用小括号来指定 子表达式(也叫作 分组),而后你就能够指定这个子表达式的重复次数了,你也能够对子表达式进行其它一些操做(后面会有介绍)。
(\d{1,3}\.){3}\d{1,3}是一个 简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它: \d{1,3}匹配 1到3位的数字(\d{1,3}\.}{3}匹配 三位数字加上一个英文句号(这个总体也就是这个分组)重复3次,最后再加上 一个一到三位的数字( \d{1,3})。
不幸的是,它也将匹配 256.300.888.999这种不可能存在的IP地址(IP地址中每一个数字都不能大于255。题外话,好像反恐24小时第三季的编剧不知道这一点,汗...)。若是能使用算术比较的话,或许能简单地解决这个问题,可是正则表达式中并不提供关于数学的任何功能,因此只能使用冗长的分组,选择,字符类来描述一个正确的IP地址: ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
理解这个表达式的关键是理解 2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你本身应该能分析得出来它的意义。

后向引用

使用小括号指定一个子表达式后, 匹配这个子表达式的文本(也就是此分组捕获的内容)能够在表达式或其它程序中做进一步的处理。默认状况下,每一个分组会自动拥有一个 组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
后向引用用于重复搜索前面某个分组匹配的文本。例如, \1表明 分组1匹配的文本
请看示例:
\b(\w+)\b\s+\1\b能够用来匹配 重复的单词,像 go go, kitty kitty。首先是 一个单词,也就是 单词开始处和结束处之间的多于一个的字母或数字( \b(\w+)\b),而后是 1个或几个空白符( \s+),最后是 前面匹配的那个单词( \1)。
你也能够本身指定子表达式的 组名。要指定一个子表达式的组名,请使用这样的语法: (?<Word>\w+)(或者把尖括号换成 '也行: (?'Word'\w+)),这样就把 \w+的组名指定为 Word了。要反向引用这个分组 捕获的内容,你可使用 \k<Word>,因此上一个例子也能够写成这样: \b(?<Word>\w+)\b\s+\k<Word>\b
使用小括号的时候,还有不少特定用途的语法。下面列出了最经常使用的一些:
表4.分组语法 捕获 位置指定 注释
(exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也能够写成(?'name'exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
(?#comment) 这种类型的组不对正则表达式的处理产生任何影响,用于提供注释让人阅读
咱们已经讨论了前两种语法。第三个 (?:exp)不会改变正则表达式的处理方式,只是这样的组匹配的内容 不会像前两种那样被捕获到某个组里面

零宽断言

接下来的四个用于查找在某些内容(但并不包括这些内容)以前或以后的东西,也就是说它们像 \b, ^, $那样用于指定一个位置,这个位置应该知足必定的条件( 断言),所以它们也被称为 零宽断言。最好仍是拿例子来讲明吧:
(?=exp)也叫 零宽度正预测先行断言,它 断言自身出现的位置的后面能匹配表达式exp。好比 \b\w+(?=ing\b),匹配 以ing结尾的单词的前面部分(除了ing之外的部分),如查找 I'm singing while you're dancing.时,它会匹配 singdanc
(?<=exp)也叫 零宽度正回顾后发断言,它 断言自身出现的位置的前面能匹配表达式exp。好比 (?<=\bre)\w+\b会匹配 以re开头的单词的后半部分(除了re之外的部分),例如在查找 reading a book时,它匹配 ading
假如你想要给一个很长的数字中每三位间加一个逗号(固然是从右边加起了),你能够这样查找须要在前面和里面添加逗号的部分: ((?<=\d)\d{3})*\b,用它对 1234567890进行查找时结果是 234567890
下面这个例子同时使用了这两种断言: (?<=\s)\d+(?=\s)匹配 以空白符间隔的数字(再次强调,不包括这些空白符)

负向零宽断言

前面咱们提到过怎么查找 不是某个字符或不在某个字符类里的字符的方法(反义)。可是若是咱们只是想要 确保某个字符没有出现,但并不想去匹配它时怎么办?例如,若是咱们想查找这样的单词--它里面出现了字母q,可是q后面跟的不是字母u,咱们能够尝试这样:
\b\w*q[^u]\w*\b匹配 包含后面不是字母u的字母q的单词。可是若是多作测试(或者你思惟足够敏锐,直接就观察出来了),你会发现,若是q出如今单词的结尾的话,像 Iraq, Benq,这个表达式就会出错。这是由于 [^u]总要匹配一个字符,因此若是q是单词的最后一个字符的话,后面的 [^u]将会匹配q后面的单词分隔符(多是空格,或者是句号或其它的什么),后面的 \w*\b将会匹配下一个单词,因而 \b\w*q[^u]\w*\b就能匹配整个 Iraq fighting负向零宽断言能解决这样的问题,由于它只匹配一个位置,并不 消费任何字符。如今,咱们能够这样来解决这个问题: \b\w*q(?!u)\w*\b
零宽度负预测先行断言 (?!exp)断言此位置的后面不能匹配表达式exp。例如: \d{3}(?!\d)匹配 三位数字,并且这三位数字的后面不能是数字\b((?!abc)\w)+\b匹配 不包含连续字符串abc的单词
同理,咱们能够用 (?<!exp), 零宽度正回顾后发断言断言此位置的前面不能匹配表达式exp(?<![a-z])\d{7}匹配 前面不是小写字母的七位数字
一个更复杂的例子: (?<=<(\w+)>).*(?=<\/\1>)匹配 不包含属性的简单HTML标签内里的内容(<?(\w+)>)指定了这样的 前缀被尖括号括起来的单词(好比多是<b>),而后是 .*(任意的字符串),最后是一个 后缀 (?=<\/\1>)。注意后缀里的 \/,它用到了前面提过的字符转义; \1则是一个反向引用,引用的正是 捕获的第一组,前面的 (\w+)匹配的内容,这样若是前缀其实是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀自己)。

注释

小括号的另外一种用途是能过语法 (?#comment)来包含注释。例如: 2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)
要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的全部文本都将被当成注释忽略掉。
例如,咱们能够前面的一个表达式写成这样:
      (?<=    # 断言要匹配的文本的前缀
      <(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
      )       # 前缀结束
      .*      # 匹配任意文本
      (?=     # 断言要匹配的文本的后缀
      <\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
      )       # 后缀结束
    

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,一般的行为是(在使整个表达式能获得匹配的前提下)匹配 尽量多的字符。考虑这个表达式: a.*b,它将会匹配 最长的以a开始,以b结束的字符串。若是用它来搜索 aabab的话,它会匹配整个字符串 aabab。这被称为 贪婪匹配。
有时,咱们更须要 懒惰匹配,也就是匹配 尽量少的字符。前面给出的限定符均可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ?。这样 .*?就意味着 匹配任意数量的重复,可是在能使整个匹配成功的前提下使用最少的重复。如今看看懒惰版的例子吧:
a.*?b匹配 最短的,以a开始,以b结束的字符串。若是把它应用于 aabab的话,它会匹配 aabab(为何第一个匹配是aab而不是ab?简单地说,由于正则表达式有另外一条规则,比懒惰/贪婪规则的优先级更高:最早开始的区配最有最大的优先权——The Match That Begins Earliest Wins)。
表5.懒惰限定符
*? 重复任意次,但尽量少重复
+? 重复1次或更屡次,但尽量少重复
?? 重复0次或1次,但尽量少重复
{n,m}? 重复n到m次,但尽量少重复
{n,}? 重复n次以上,但尽量少重复

处理选项

上面介绍了几个选项如忽略大小写,处理多行等,这些选项能用来改变处理正则表达式的方式。下面是.Net中经常使用的正则表达式选项:
表6.经常使用的处理选项 名称说明
IgnoreCase(忽略大小写) 匹配时不区分大小写。
Multiline(多行模式) 更改^$的含义,使它们分别在任意一行的行首和行尾匹配,而不只仅在整个字符串的开头和结尾匹配。
Singleline(单行模式) 更改.的含义,使它与每个字符匹配(包括换行符\n)。
IgnorePatternWhitespace(忽略空白) 忽略表达式中的非转义空白并启用由#标记的注释。
RightToLeft(从右向左查找) 匹配从右向左而不是从左向右进行。
ExplicitCapture(显式捕获) 仅捕获已被显式命名的组。
ECMAScript(JavaScript兼容模式) 使表达式的行为与它在JavaScript里的行为一致。
一个常常被问到的问题是:是否是只能同时使用多行模式和单行模式中的一种?答案是:不是。这两个选项之间没有任何关系,除了它们的名字比较类似(以致于让人感到疑惑)之外。

平衡组/递归匹配

注意:这里介绍的平衡组语法是由.Net Framework支持的;其它语言/库不必定支持这种功能,或者支持此功能但须要使用不一样的语法。
有时咱们须要匹配像 ( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用 \(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里咱们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,好比 ( 5 / ( 3 + 2 ) ) ),那咱们的匹配结果里二者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?
为了不 (\(把你的大脑完全搞糊涂,咱们仍是用尖括号代替圆括号吧。如今咱们的问题变成了如何把 xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来?
这里须要用到如下的语法构造:
  • (?'group') 把捕获的内容命名为group,并压入堆栈
  • (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,若是堆栈原本为空,则本分组的匹配失败
  • (?(group)yes|no) 若是堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,不然继续匹配no部分
  • (?!) 零宽负向先行断言,因为没有后缀表达式,试图匹配老是失败
若是你不是一个程序员(或者你是一个对堆栈的概念不熟的程序员),你就这样理解上面的三种语法吧:第一个就是在黑板上写一个 "group",第二个就是从黑板上擦掉一个"group",第三个就是看黑板上写的还有没有"group",若是有就继续匹配yes部分,不然就匹配 no部分。
咱们须要作的是每碰到了左括号,就在黑板上写一个"group",每碰到一个右括号,就擦掉一个,到了最后就看看黑板上还有没有--若是有那就证实左括号比右括号多,那匹配就应该失败。
<                         #最外层的左括号
    [^<>]*                #最外层的左括号后面的不是括号的内容
    (
        (
            (?'Open'<)    #碰到了左括号,在黑板上写一个"Open"
            [^<>]*       #匹配左括号后面的不是括号的内容
        )+
        (
            (?'-Open'>)   #碰到了右括号,擦掉一个"Open"
            [^<>]*        #匹配右括号后面不是括号的内容
        )+
    )*
    (?(Open)(?!))         #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";若是还有,则匹配失败
>                         #最外层的右括号
平衡组的一个最多见的应用就是匹配HTML,下面这个例子能够匹配 嵌套的<div>标签<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.

还有些什么东西没提到

我已经描述了构造正则表达式的大量元素,还有一些我没有提到的东西。下面是未提到的元素的列表,包含语法和简单的说明。你能够在网上找到更详细的参考资料来学习它们--当你须要用到它们的时候。若是你安装了MSDN Library,你也能够在里面找到关于.net下正则表达式详细的文档。
表7.还没有详细讨论的语法
\a 报警字符(打印它的效果是电脑嘀一声)
\b 一般是单词分界位置,但若是在字符类里使用表明退格
\t 制表符,Tab
\r 回车
\v 竖向制表符
\f 换页符
\n 换行符
\e Escape
\0nn ASCII代码中八进制代码为nn的字符
\xnn ASCII代码中十六进制代码为nn的字符
\unnnn Unicode代码中十六进制代码为nnnn的字符
\cN ASCII控制字符。好比\cC表明Ctrl+C
\A 字符串开头(相似^,但不受处理多行选项的影响)
\Z 字符串结尾或行尾(不受处理多行选项的影响)
\z 字符串结尾(相似$,但不受处理多行选项的影响)
\G 当前搜索的开头
\p{name} Unicode中命名为name的字符类,例如\p{IsGreek}
(?>exp) 贪婪子表达式
(?<x>-<y>exp) 平衡组
(?im-nsx:exp) 在子表达式exp中改变处理选项
(?im-nsx) 为表达式后面的部分改变处理选项
(?(exp)yes|no) 把exp看成零宽正向先行断言,若是在这个位置能匹配,使用yes做为此组的表达式;不然使用no
(?(exp)yes) 同上,只是使用空表达式做为no
(?(name)yes|no) 若是命名为name的组捕获到了内容,使用yes做为表达式;不然使用no
(?(name)yes) 同上,只是使用空表达式做为no

术语的参考

字符
程序处理文字时最基本的单位,多是字母,数字,标点符号,空格,换行符,汉字等等。
字符串
0个或更多个字符的序列。
文本
文字,字符串。
匹配
符合规则,检验是否符合规则,符合规则的部分。
断言
声明一个应该为真的事实。只有当断言为真时才会对正则表达式继续进行匹配。

网上的资源及本文参考文献

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息