正则表达式

2014-11-26 17:10:07   最后更新: 2018-11-08 15:35:26   访问数量:709




在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。

最简单的例子就是 hi,可以查找到文本中全部的 hi 字符串,如果忽略大小写,就可以匹配全部的 hi、Hi、HI、hI,同时可以匹配到 him、history 等等文本中的 hi

常用元字符

元字符意义反义意义
\w匹配字母、数字、下划线或汉字\W匹配非数字、字母、下划线或汉字
\s匹配任意空白字符\S匹配任意非空白字符
\d匹配数字\D匹配非数字
\b匹配单词开始或结束\B匹配不是单词开始或结束的位置
^匹配字符串开始$匹配字符串结束
.匹配除换行外的任意字符  

示例讲解

\bhi\b

可以精确匹配出来 hi

\b -- 匹配一个位置,他的前后两个字符不全是\w

这样,使用 \bhi\b 就可以精确匹配出 hi 这个单词了,而不会让 high 也被匹配出来

 

\bhi\b.*\bLucy\b

可以匹配出来 "hi,Lucy"、"hi Lucy"、"hi! Lucy"

 

. -- 匹配任意字符

* -- 重复0次以上

 

0\d\d-\d\d\d\d\d\d\d\d

可以匹配出来 010-88658742 这样的电话号码

 

\d -- 匹配数字,相当于[0-9]

 

0\d{2}-\d{8}

即 \d 必须分别在 - 前后出现 2 次和 8 次

 

^\d{5,12}$

可以匹配出来5到12位纯数字

 

unibetter\.com

有的时候我们并不希望 . 被解释成元字符,如上例只是想匹配出 unibetter.com,这时我们就需要使用转义字符

 

<a[^>]+>

匹配用尖括号括起来的以 a 开头的字符串

其他元字符

元字符意义
\将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,"n"匹配字符"n"。"\n"匹配换行符。序列"\\"匹配"\","\("匹配"("。
^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与"\n"或"\r"之后的位置匹配。
$匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与"\n"或"\r"之前的位置匹配。
*零次或多次匹配前面的字符或子表达式。例如,zo* 匹配"z"和"zoo"。* 等效于 {0,}。
+一次或多次匹配前面的字符或子表达式。例如,"zo+"与"zo"和"zoo"匹配,但与"z"不匹配。+ 等效于 {1,}。
?零次或一次匹配前面的字符或子表达式。例如,"do(es)?"匹配"do"或"does"中的"do"。? 等效于 {0,1}。
{n}n 是非负整数。正好匹配 n 次。例如,"o{2}"与"Bob"中的"o"不匹配,但与"food"中的两个"o"匹配。
{n,}n 是非负整数。至少匹配 n 次。例如,"o{2,}"不匹配"Bob"中的"o",而匹配"foooood"中的所有 o。"o{1,}"等效于"o+"。"o{0,}"等效于"o*"。
{n,m}M 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,"o{1,3}"匹配"fooooood"中的头三个 o。'o{0,1}' 等效于 'o?'。注意:您不能将空格插入逗号和数字之间。
?当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是"非贪心的"。"非贪心的"模式匹配搜索到的、尽可能短的字符串,而默认的"贪心的"模式匹配搜索到的、尽可能长的字符串。例如,在字符串"oooo"中,"o+?"只匹配单个"o",而"o+"匹配所有"o"。
.匹配除"\n"之外的任何单个字符。若要匹配包括"\n"在内的任意字符,请使用诸如"[\s\S]"之类的模式。
(pattern)匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果"匹配"集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用"\("或者"\)"。
(?:pattern)匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符 (|) 组合模式部件的情况很有用。例如,'industr(?:y|ies) 是比 'industry|industries' 更经济的表达式。
(?=pattern)执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,'Windows (?=95|98|NT|2000)' 匹配"Windows 2000"中的"Windows",但不匹配"Windows 3.1"中的"Windows"。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
(?!pattern)执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,'Windows (?!95|98|NT|2000)' 匹配"Windows 3.1"中的 "Windows",但不匹配"Windows 2000"中的"Windows"。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
x|y匹配 x 或 y。例如,'z|food' 匹配"z"或"food"。'(z|f)ood' 匹配"zood"或"food"。
[xyz]字符集。匹配包含的任一字符。例如,"[abc]"匹配"plain"中的"a"。
[^xyz]反向字符集。匹配未包含的任何字符。例如,"[^abc]"匹配"plain"中"p","l","i","n"。
[a-z]字符范围。匹配指定范围内的任何字符。例如,"[a-z]"匹配"a"到"z"范围内的任何小写字母。
[^a-z]反向范围字符。匹配不在指定的范围内的任何字符。例如,"[^a-z]"匹配任何不在"a"到"z"范围内的任何字符。
\b匹配一个字边界,即字与空格间的位置。例如,"er\b"匹配"never"中的"er",但不匹配"verb"中的"er"。
\B非字边界匹配。"er\B"匹配"verb"中的"er",但不匹配"never"中的"er"。
\cx匹配 x 指示的控制字符。例如,\cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是"c"字符本身。
\d数字字符匹配。等效于 [0-9]。
\D非数字字符匹配。等效于 [^0-9]。
\f换页符匹配。等效于 \x0c 和 \cL。
\n换行符匹配。等效于 \x0a 和 \cJ。
\r匹配一个回车符。等效于 \x0d 和 \cM。
\s匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。
\S匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。
\t制表符匹配。与 \x09 和 \cI 等效。
\v垂直制表符匹配。与 \x0b 和 \cK 等效。
\w匹配任何字类字符,包括下划线。与"[A-Za-z0-9_]"等效。
\W与任何非单词字符匹配。与"[^A-Za-z0-9_]"等效。
\xn匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,"\x41"匹配"A"。"\x041"与"\x04"&"1"等效。允许在正则表达式中使用 ASCII 代码。
\num匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,"(.)\1"匹配两个连续的相同字符。
\n标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。
\nm标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 n 和 m 是八进制数字 (0-7)。
\nml当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml。
\un匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (?)。

我们常常需要对代码限定其重复次数

常用限定符
限定符意义
* 重复 0 次以上
+ 重复 1 次以上
? 重复 0 次或 1 次
{n} 重复 n 次
{n,} 重复 n 次以上
{n,m}重复 n 到 m 次

 

^\w+

匹配一行第一个单词

 

Windows\d+

匹配 Windows 后面跟至少一个数字的字符串

 

如果要查找指定的字符,就需要用到方括号了

 

[aeiou]

可以匹配任意一个英文元音字母

 

[.?!]

可以匹配 . 或 ? 或 !

 

[a-z0-9A-Z_]

可以匹配所有字母、数字和下划线

 

\(?0\d{2}[) -]?\d{8}

可以匹配 (010)88886666、022-22334455、02912345678 等格式

 

上面匹配电话号码的例子不仅可以匹配出 (010)88886666,也可以匹配出 (010-88886666,但这事实上并不是我们需要的,解决这个问题我们就需要用到分支条件

 

所谓的分支条件就是几种规则,使用 | 分开

 

0\d{2}-\d{8}|0\d{3}-\d{7}

这个例子就可以匹配出 010-88645673、0471-4976546 这样中国两种不同的电话号码

 

\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}

这样可以匹配出使用小括号或 - 或不用的方式,但不会出现上面提到的问题

 

和 C 语言中一样,使用分支条件会遇到短路取值的问题,比如:

\d{5}|\d{5}-\d{4}

只会匹配出 5 位数字的情况

 

重复单个字符直接在字符后面加上限定符就行了,但是有时我们需要重复多个字符,这个时候我们就需要让这多个字符变成一个子表达式,也叫分组

 

(\d{1,3}\.){3}\d{1,3}

匹配 IP 地址的格式,当然我们并不希望 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?)

 

使用小括号指定一个子表达式以后,一个子表达式捕获的文本可以在其他表达式或其他程序中进一步处理,因此,默认情况下,每个分组都会自动拥有一个组号:从左到右,以分组的左括号为标志,从 1 开始依次安排序号

所谓的后向引用指的就是重复搜索前面某个分组匹配过的文本,例如 \1 表示分组1匹配的文本

 

\b(\w+)\b\s+\1\b

匹配重复的单词,比如 go go go、hello hello 等的情况

 

除了用默认的编号来找到分组,也可以自己指定子表达式的组名:

(?<Word>\w+) 或 (?'Word'\w+) 将这个子表达式命名为 Word

 

后向引用这个表达式需要使用元字符 \k

\b(?<Word>\w+)\b\s+\k<Word>\b

 

下面是分组的几个常用的用法:

(exp) 匹配 exp,并捕获文本到自动命名的分组中
(?exp)匹配 exp,并捕获文本到分组,分组命名为 name,也可写成 (?'name'exp)
(?:exp) 匹配 exp,不捕获文本,也不给分组编号

 

很多时候我们需要查找一个位置,这个位置需要满足一定的条件,这个条件就被称为零宽断言

零宽断言
断言意义
(?=exp) 零宽度正预测断言,匹配 exp 前面的位置
(?<=exp)零宽度正回顾后发断言,匹配 exp 后面的位置
(?!exp) 零宽度负预测先行断言,匹配后面跟的不是 exp 的位置
(?零宽度负回顾后发断言,匹配前面跟的不是 exp 的位置

 

\b\w+(?=ing\b)

匹配所有以 ing 结尾的单词,比如对于下面一句话:

I'm singing while you're dancing.

会匹配出 sing 和 danc

 

(?<=\bre)\w+\b

会匹配出所有以 re 开头的单词,如 reading a book 会匹配出 ading

 

(?<=\s)\d+(?=\s)

匹配以空白符间隔的数字(再次强调,不包括这些空白符)

 

\d{3}(?!\d)

匹配三位数字,而这三位后面不能还是数字

 

(?<![a-z])\d{7}

匹配前面不是小写字母的七位数字

 

(?<=<(\w+)>).*(?=<\/\1>)

匹配不包含属性的简单HTML标签内里的内容

 

小括号的另一个用途就是注释,(?#comment) 就是注释的方法

 

有的时候我们希望匹配尽可能多或尽可能少的字符,这就分别是贪婪与懒惰的例子

 

正则表达式默认是贪婪的,比如:

a.*b

匹配以 a 开始,以 b 结束的字符串

对于 aabab,会匹配出 aabab,这就是贪婪匹配

懒惰匹配限定符
限定符意义
*? 重复任意次,但尽可能少重复
+? 重复至少一次,但尽可能少重复
?? 重复 0 次或 1 次,但尽可能少重复
{n,m}?重复 n 到 m 次,但尽可能少重复
{n,}? 至少重复 n 次,但尽可能少重复

 

有时我们需要匹配像 (100*(50+15)) 这样的可嵌套的层次性结构,这时简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容,而我们常常要匹配最长的配对括号之间的内容,我们就需要使用到堆栈:

 

(?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
(?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
(?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

 

平衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配嵌套的<div>标签:

<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.

 






技术帖      技术分享      正则表达式      regular      reg     


京ICP备15018585号