正则表达式学习
正则表达式学习
入门
\b 表示单词的界限,比如 \bweb\b 表示匹配web这个独立的单词。
. 表示匹配一个任意字符(除了换行符)
* 表示数量, 意思是 *前面的内容可以重复使用无数次 比如 .* 放在一起的意思就是 任意数量的不包含换行符的字符
+ 和*类似, 不同的是 *表示重复任意次 可能是0次 而+是匹配1次或更多次
\d 表示匹配数字 比如 0\d\d-\d\d\d\d\d\d\d\d 的意思是匹配以0开头后面是两个数字 然后再一个连字符- 然后再8个数字,这是匹配中国电话的格式 这么多的\d看起来很麻烦,也可以写成
0\d{2}-\d{8} 表示\d重复了2次和8次
\s 表示匹配任意的空白符 包括空格 制表符 换行符等
\w 表示匹配字母或数字或下划线或汉字等
来几个例子巩固一下上面的入门基础
\ba\w*\b 意思是匹配以字母a开头的单词 先是\b表示某个单词开始处 然后是字母a 就是以a 开头 然后\w 表示匹配字母 然后*是匹配无数个任意字母 然后\b结束
\d+ 匹配一个或更多的连续数字
\b\w{6}\b 匹配6个字母的单词
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
^和$都匹配一个位置 和 \b类似 相当于是\b的详细化 ^表示开头 $表示结尾 比如匹配QQ号是5-12位数字 ^\d{5,12}$ 这里的{5,12}和}{2}类似 {2}表示不多不少重复2次 {5,12}表示重复的次数不少于5次 不多于12次
以上那些. \w \s \d之类的都是叫做 元字符
字符转义
如果你想查找元字符本身的话 比如想找 . * 就出了问题, 没办法指定 因为他们会被解释成别的意思 这时你就要用 \ 来进行转义 于是要写成\. \*才可以 \也是特殊 想找\就是 \\
比如 dddccc\.net 匹配dddccc.net C:\\WINDOWS 匹配 C:\WINDOWS
重复
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
看个例子 Windows \d+ 匹配Windows后面跟一个或者多个数字
^\w+ 匹配一行的第一个单词
字符类
\(?0\d{2}[)-]?\b{8} 匹配几种格式的电话号码 (010)81817171 或 021-81817171 或02181817171
分析一下 转义字符\( 可以出现0次或1次是使用? 然后是0 后面跟2个数字 \d{2} 然后是) 或 - 或空格中的一个 出现一次或不出现 ? 最后是8个数字\d{8}
分枝条件
但是还会有其他问题 比如上面的匹配还可以匹配到 010)81817171 (010-81817171 这种不正确的格式
0\d{2}-\d{8}|0\d{3}-\d{7} 匹配两种以连字符分割的电话号码 010-88888888 或 0111-7777777
\(?0\d{2}\)?[-]?\d{8}|0\d{2}[-]?\d{8} 匹配3位区号的电话号码 其中区号可以用小括号扩起来 也可以不用 区号与本地号码间可以用连字符或空格间隔 也可以没有
\d{5}-\d{4}|\d{5} 匹配美国邮编 美国邮编的规则是5位数字或者连字符间隔的9位数字
使用分枝条件时 要注意各个条件顺序 如果改成 \d{5}|\d{5}-\d{4}就只会匹配5位邮编或者9位邮编的前5位 因为匹配分支条件时 将会从左到右地测试每个条件 如果满足了某个分支条件 就不再去管其他的条件的
分组
(\d{1,3}\.){3}\d{1,3} 匹配IP 分析一下 \d{1,3}匹配1到3位的数字 (\d{1,3}\.){3}匹配3位数字加上一个点 然后重复3次 最后在加上一个 1到3位数字\d{1,3}
但是 又但是了 可能会匹配出256.300.888.999 这种不能存在的IP地址 所以还要再详细((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
反义
有时需要匹配不属于字符集的字符串 就需要反义 比如匹配除了数字以外的任意字符 此时就需要反义更简单
代码/语法 | 说明 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
比如 \S+ 匹配不包含空白符的字符串
<a[^>]+> 匹配用尖括号括起来的以a开头的字符串
后向引用
使用小括号指定一个子表达式后 匹配这个子表达式的文本 也就是此分组捕获的内容 可以在表达式或其他程序中作进一步处理 默认是 每个分组都会自动拥有一个组号 规则是从左向右 以分组的左括号为标志 第一个出现的分组的组号为1 第二个为2 以此类推
后向引用 用于重复搜索前面某个分组匹配的文本 例如 1\ 表示 分组1匹配的文本 例如
\b(\w+)\b\s+\1\b 匹配重复的单词 像 go go 或 hello hello 这个表达式首先是一个单词 也就是 单词开始处和结束处之间的多余一个的字母或数字\b(\w+)\b 这个单词会被捕获到编号为1的分组中 然后是 1个或几个空白符 \s+ 最后是分组1中捕获的内容 也就是前面匹配的那个单词 \1
你也可以自己指定子表达式的组名 要指定组名 请使用这样的语法 (?<Word>\w+) 尖括号换成'也可以 (?'Word'\w+) 这样就把\w+的组名指定成Word了
要反响引用这个分组捕获的内容 可以使用\k<Word> 所以上个例子可以写成 \b(?<Word>\w+)\b\s+\k<Word>\b
使用小括号时候 还有很多特定用途的语法 如下
分类 | 代码/语法 | 说明 |
---|---|---|
捕获 | (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 时 会 匹配出 sing和 danc
(?<=exp)也叫 零宽度正回顾后发断言 它 断言自身出现的位置的前面能匹配表达式exp 比如 (?<=\bre)\w+\b 会匹配 以re开头的单词的后半部分(除re以外的部分) 如 查找 reading a book时会匹配出ading
加入你想给很长数字中每3位间加一个逗号(当然是从右边加起了) 你可以这样查找需要在前面和里面添加逗号的部分: ((?<=\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} 匹配前面不是小写字母的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 的话 它会匹配整个字符串 这被称为 贪婪 匹配
有时 我们更需要懒惰匹配 也就是匹配尽可能少的字符 前面给出的限定符都可以转化为懒惰匹配模式 只要在它后面加上一个? 这样 .*? 就意味着 匹配任意数量的重复 但是在能使整个匹配成功的前提下使用最少的重复 懒惰的例子:
a.*?b匹配最短的 以a开始 以b结束的字符串 如果把它用于 aabab 的话 它会匹配 aab (第一到第三个字符) 和 ab (第四到第五个字符)
代码/语法 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
处理选项
名称 | 说明 |
---|---|
IgnoreCase(忽略大小写) | 匹配时不区分大小写。 |
Multiline(多行模式) | 更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
Singleline(单行模式) | 更改.的含义,使它与每一个字符匹配(包括换行符\n)。 |
IgnorePatternWhitespace(忽略空白) | 忽略表达式中的非转义空白并启用由#标记的注释。 |
ExplicitCapture(显式捕获) | 仅捕获已被显式命名的组。 |
平衡组/递归匹配
还有些什么东西没提到
代码/语法 | 说明 |
---|---|
\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 |