摘录与 《正则表达式必知必会》
零、快速上手
-
[abc]
匹配单个字符a
、b
、c
-
.
匹配除换行符(\n、\r
)之外的任何单个字符。 -
[0-9]
等价[0123456789]
含义是匹配一个数字,输入 123。 匹配三个结果1
、2
、3
。-
(连字符)是一个特殊的元字符,它只有出现在[
和]
之间的时候才是元字符 -
\d
等价上面的[0-9]
\w
匹配字母、数字、下划线。等价于[A-Za-z0-9_]
。
^[0-9]
表示匹配一个非数字。^
有取反的意思
[A-Za-z0-9]
等价[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]
. -
*
匹配前面的子表达式零次或多次。例如,zo*
能匹配z
以及zoo
。* 等价于{0,}
。 -
+
匹配前面的子表达式一次或多次。例如,zo+
能匹配zo
以及zoo
,但不能匹配z
。+ 等价于 {1,}
。 -
?
匹配前面的子表达式零次或一次。例如,do(es)?
可以匹配do
或does
。? 等价于 {0,1}
。 -
x|y
匹配x
或y
。例如,z|food
能匹配z
或food
。(z|f)ood
则匹配zood
或food
-
()
表示子表达式,比如我有个内容<h1>Welcome to Echo!</h1> <h2>Welcome to Echo!</h1>
,使用<(h[12])>.*</\1>
只会匹配<h1>Welcome to Echo!</h1>
, 不会匹配<h2>Welcome to Echo!</h1>
。(h[12])
是一个子表达式,\1
这里等价于h1
,表示开始标签和结束标签内容应该一致。 -
防止过度匹配
This offer is not available to customers living in <b>AK</b> and <b>HI</b>. 正则:<[Bb]>.*<\/[Bb]> (vscode 里面这种也默认是懒惰型,会返回两个结果) 匹配结果:<b>AK</b> and <b>HI</b> (一个结果,认为“AK</b> and <b>HI” 匹配 .*) 正则:<[Bb]>.*?<\/[Bb]> 匹配结果:<b>AK</b> <b>HI</b> (两个结果)*?
是*
的懒惰型版本。 -
环视,也有叫零宽断言 。加入我要取出
<title>Ben Forta's Homepage</title> </head>title
标签中的内容,我们正则可以这样写(?<=<title>).*(?=</title>)
-
?=
是向前查看,.+(?=:)
匹配https://mail.forta.com/
中的https
。任何子表达式都可以转换为向前查看表达式,只要在其之前加上一个?=
即可。 -
?<=
是先后查看,(?<=\$)[0-9.]+
匹配ABC01: $23.45
中的23.45
。 向后查看模式则只能是固定长度 。几乎所有的正则表达式实现都有此限制。 -
A-Z
,匹配从A
到Z
的所有大写字母。 -
a-z
,匹配从a
到z
的所有小写字母。 -
A-F
,匹配从A到F的所有大写字母。 -
-
(连字符)是一个特殊的元字符,它只有出现在[
和]
之间的时候才是 元字符 。在字符集合以外的地方,-只是一个普通字符,只能与-本身相匹配。因此,在正则表达式里,-字符不需要被转义。
一、正则表达式用途
正则表达式语言是内置于其他语言或软件产品里的“迷你”语言 。主要用户文本处理(查找替换)。
二、匹配单个字符
Hello, my name is Ben. Please visit my website at http://www.forta.com/. 正则:Ben // 则表达式是区分字母大小写的,所以Ben不匹配ben 匹配结果:Ben匹配任意字符
.
字符(英文句号)可以匹配任意单个字符
匹配特殊字符(转移字符)
转义符
\
,比如
\.
表示
.
本身,
\\
表示
\
,
\*
表示
*
三、匹配一组字符
[Rr]
负责匹配字母
R
和
r
,
[Ee]
负责匹配字母
E
和
e
利用字符集合区间
[]
表示
一个
字符的
集合
,这意味着表达式将匹配方括号中列出的任何一个字符。
[0123456789]
匹配一个数字,等价与
[0-9]
,比如匹配数字
1
,不能匹配
12
。
[A-Za-z0-9]
等价
[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]
[^0-9]
表示匹配的是任何不是数字的字符
四、使用元字符
var myArray = new Array(); if (myArray[10086] == 0) { 正则:myArray\[\d\] 匹配:myArray[10086]
匹配字母数字
1A213B A1C2E3 正则:\w\d\w\d\w\d 匹配:A1C2E3匹配空白字符
abcdef ab def 正则:ab\s 匹配:ab def匹配十六进制或八进制数值
十六进制值(基数为
16
)要用前缀\x
来给出。比如说,\x0A
(对应于ASCII字符10,也就是换行符)等价于\n
。八进制值(基数为
8
)要用前缀\0
来给出,数值本身可以是两位或三位数字。比如说,\011
(对应于ASCII
字符9
,也就是制表符)等价于\t
。PS: 有不少正则表达式实现还允许使用
\c
前缀来指定各种控制字符。比如说,\cZ
可以匹配Ctrl-Z
。不过,在实践中,极少会用到这种语法。使用POSIX字符类
主要用于
grep
、vim
这类工具支持[:alnum:]:
匹配所有的字母和数字字符。相当于[A-Za-z0-9]
。[:alpha:]:
匹配所有的字母字符。相当于[A-Za-z]
。[:digit:]:
匹配所有的数字字符。相当于[0-9]
。[:space:]:
匹配所有的空白字符,包括空格、制表符、换行符等。[:blank:]:
匹配所有的空格和制表符字符。[:lower:]:
匹配所有的小写字母。[:upper:]:
匹配所有的大写字母。[:print:]:
匹配所有可打印的字符。[:punct:]:
匹配所有的标点字符。[:graph:]:
匹配所有的可见字符,包括字母、数字、标点符号等。
五、重复匹配
Send personal email to [email protected]. For questions about a book use [email protected]. Feel free to send unsolicited email to [email protected] (wouldn't it be nice if it were that simple, huh?). 正则:[\w.]+@[\w.]+\.\w+ 匹配:[email protected] [email protected] [email protected]
[\w.]+
匹配字母数字字符、下划线和.
的一次或多次重复出现匹配零个或多个字符
Hello [email protected] is my email address. [email protected] 正则:\w+[\w.]*@[\w.]+\.\w+ 匹配:[email protected] [email protected] The URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instead. 正则:http[s]*:// 等价 https?:// 匹配:http:// https://
[\w.]*
匹配.
或字母数字字符的零次或多次重复出现
https?://
,?
在这里的含义是:前面的字符s
要么不出现,要么最多出现一次。匹配的重复次数
{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?
。请注意在逗号和两个数之间不能有空格。防止过度匹配
This offer is not available to customers living in <b>AK</b> and <b>HI</b>. 正则:<[Bb]>.*<\/[Bb]> (vscode 里面这种也默认是懒惰型,会返回两个结果) 匹配结果:<b>AK</b> and <b>HI</b> (一个结果,认为“AK</b> and <b>HI” 匹配 .*) 正则:<[Bb]>.*?<\/[Bb]> 匹配结果:<b>AK</b> <b>HI</b> (两个结果) 贪婪型量词 | 懒惰型量词 ------------- | ------------- * | *? + | +? {n,} | {n,}?
*?
是*
的懒惰型版本。六、位置匹配
The cat scattered his food all over the room. 正则:\bcat\b 匹配结果:cat
\b
| 匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b
可以匹配never
中的er
,但不能匹配verb
中的er
。简单说\b
匹配的是字符之间的一个位置:一边是单词(能够被\w匹配的字母数字字符和下划线),另一边是其他内容(能够被\W匹配的字符)。字符串边界
This is bad, real bad! <?xml version="1.0" encoding="UTF-8" ?> <wsdl:definitions targetNamespace="http://tips.cf" xmlns:impl="http://tips.cf" xmlns:intf="http://tips.cf" xmlns:apachesoap="http://xml.apache.org/xml-soap" 正则:^\s*<\?xml.*\?> 匹配结果:没有匹配到任何东西,必须是 <\?xml.*\?> 开头的才行。许多正则表达式都支持使用一些特殊的元字符去改变另外一些元字符的行为,
<script> function doSpellCheck(form, field) { // Make sure not empty if (field.value == '') { return false; // Init var windowName='spellWindow'; var spellCheckURL='spell.cfm?formname=comment&fieldname='+ field.name; // Done return false; </script> 正则:(?m)^\s*\/\/.*$ 匹配结果:所有注释 “// Make sure not empty”,“// Init”,“// Done”(?m)
就是其中之一,它可用于启用多行模式(multiline mode)
。多行模式迫使正则表达式引擎将换行符视为字符串分隔符包括
JavaScript
在内的许多正则表达式实现都不支持(?m)
七、使用子表达式
Hello, my name is Ben Forta, and I am the author of multiple books on SQL (including MySQL, Oracle PL/SQL, and SQL Server T-SQL), Regular Expressions, and other subjects. 正则:( ){2,} 匹配结果: 2000-10-11 1999-08-01 1899-11-28 正则:(19|20)\d{2} 匹配结果:2000 1999 ip地址正则 (((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5])) 匹配: 100.233.233.6
( )
是一个子表达式,它被视为单一的实体。因此,紧随其后的{2,}将作用于整个子表达式(而不仅仅是分号)八、反向引用
<h1>Welcome to Echo!</h1> <h2>Welcome to Echo!</h1> 正则:<[hH]([1-6])>.*?<\/[hH]\1> 匹配结果:<h1>Welcome to Echo!</h1>模式最后一部分是
\1
,这是对前面那个子表达式的反向引用,\1
匹配的内容与第一个分组匹配的内容一样。所以<h1> ... </h2>
匹配不上。反向引用匹配通常从 1
开始计数(\1
、\2
等)。在许多实现里,第0
个匹配(\0
)可以用来代表整个正则表达式。一些比较新的正则表达式实现还支持“命名捕获”( 313-555-1234 248-555-9999 810-555-9000 正则:(\d{3})-(\d{3})-(\d{4}) 替换正则:$3-$1-$2 替换结果: 1234-313-555 9999-248-555 9000-810-555named capture
):给某个子表达式起一个唯一的名称,随后用该名称(而不是相对位置)来引用这个子表达式。目前很多语音还没支持大小写转换
<h1>Welcome to my Homepage</h1> </body> 正则:(\d{3})-(\d{3})-(\d{4}) 替换正则:$1\U$2\E$3 (VScode 替换可以不用加\E) 替换结果: <h1>WELCOME TO MY HOMEPAGE</h1> </body> 匹配结果:<title>Ben Forta's Homepage</title>明知是自己不需要的东西,还把它们检索出来,然后再手动删除,这种做法毫无意义。你真正需要的是想办法构造出一种模式,该模式中包含一些不用被返回的匹配——这些匹配是为了找出正确的匹配位置,其自身不属于最终的匹配结果。换句话说,你需要进行“环视”。
<title>Ben Forta's Homepage</title> </head> 正则:(?<=<title>).*(?=</title>) 匹配结果:Ben Forta's Homepage
?=
是向前查看,.+(?=:)
匹配https://mail.forta.com/
中的https
。任何子表达式都可以转换为向前查看表达式,只要在其之前加上一个?=
即可。
?<=
是先后查看,(?<=\$)[0-9.]+
匹配ABC01: $23.45
中的23.45
。向后查看模式则只能是固定长度。几乎所有的正则表达式实现都有此限制。(?<!pattern)
| 反向否定预查,与正向否定预查类似,只是方向相反。例如(?<!95|98|NT|2000)Windows
能匹配3.1Windows
中的Windows
,但不能匹配2000Windows
中的Windows
。(?!pattern)
| 正向否定预查(asser
),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如Windows(?!95|98|NT|2000)
能匹配Windows3.1
中的Windows
,但不能匹配Windows2000
中的Windows
。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。向前查看和向后查看其实是有返回结果的,只不过结果永远都是零长度字符串。因此,环视操作有时也被称为零宽度( zero-width
)匹配操作。用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b,^,$
那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。
(?=exp)
也叫零宽度正预测先行断言。(?<=exp)
也叫零宽度正回顾后发断言。十、嵌入式条件
反向引用条件仅在一个前面的子表达式得以匹配的情况下才允许使用另一个表达式。比如一个字符串
<a href="/home"><img src="/images/home.gif"></a> <img src="/images/spacer.gif"> <a href="/search"><img src="/images/search.gif"></a> <img src="/images/spacer.gif"> <a href="/help"><img src="/images/help.gif"></a> 正则:(<[Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*<\/[Aa]>)(123)
,左右括号需要同时出现才行
(<[Aa]\s+[^>]+>\s*)?
匹配一个<A>
或<a>
标签(以及可能存在的任意属性),这个标签可有可无(因为这个子表达式的最后有一个?
)。接下来,<[Ii][Mm][Gg]\s+[^>]+>
匹配一个<img>
标签(大小写均可)及其任意属性。(?(1)\s*<\/[Aa]>)
的起始部分是一个条件:?(1)
表示仅当第一个反向引用(<A>
标签)存在,才继续匹配\s*<\/[Aa]>
(换句话说,只有当第一个<A>
标签匹配成功,才去执行后面的匹配)。如果(1)
存在,\s*<\/[Aa]>
匹配结束标签</A>
之后出现的任意空白字符。用来定义这种条件的语法是
(?(backreference)true|false)
。此语法接受一个条件和两个分别在符合/不符合该条件时执行的表达式。123-456-7890 (123)456-7890 (123)-456-7890 (123-456-7890 1234567890 123 456 7890 (\()?\d{3}(?(1)\)|-)\d{3}-\d{4}
从结果看,问题解决了,但它是如何解决的呢?和前面一样,
(\()?
负责检查左括号,但我们这次将其放入了括号中,这样就得到了一个子表达式。随后的\d{3}
匹配3
位数字的区号。依赖于是否满足条件,(?(1)\)|-)
匹配)
或-
。如果(1)
存在(也就是找到了一个左括号),必须匹配\)
;否则,必须匹配-
。这样一来,括号就只能成对出现。如果没有使用括号,电话区号和其余数字之间的-分隔符必须被匹配。为什么没有匹配第4
行?因为左括号(没有与之匹配的右括号),所以嵌入条件被视为无关文本,完全被忽略了。并非所有的正则表达式实现都支持条件处理。
十一、常用正则表达式
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))
https?:\/\/[-\w.]+(:\d+)?(\/([\w\/_.]*)?)?
https?:\/\/
匹配http://
或https://
(?
使得字符s
成为可选项)。[-\w.]+
匹配主机名。(:\d+)?
匹配一个可选的端口号(参见上例中的第2
行和第6
行)。(\/([\w\/_.]*)?)?
匹配路径:外层的子表达式匹配/
(如果存在的话),内层的子表达式匹配路径本身。如你所见,这个模式无法处理查询字符串,也不能正确解读嵌在URL
之中的“username:password
”(用户名:密码)。不过,它已经足以处理绝大多数的URL
了(匹配主机名、端口号和路径)。完整的URL:
https?:\/\/(\w*:\w*@)?[-\w.]+(:\d+)?(\/([\w\/_.]*(\?\S+)?)?)?
该模式是在前一个例子的基础上改进而来的。这次紧跟在
https?: \/\/
后面的是(\w*:\w*@)?
,它匹配嵌入在URL
之中的用户名和密码(用户名和密码要用:
隔开,后面还要跟上一个@
字符),参见这个例子中的第4
行。另外,路径之后的(\?\S+)?
负责匹配查询字符串,出现在?后面的文本是可选的,这可以使用?来表示。就性能来说,越复杂的模式,执行速度越慢。如果不需要额外的功能,还是不使用它比较好。
电子邮件地址:
(\w+\.)*\w+@(\w+\.)+[A-Za-z]+
决定电子邮件地址格式有效性的规则极其复杂。该模式无法验证所有可能的电子邮件地址。比如说,这个模式会认为
[email protected]
是有效的(显然无效),也不允许主机名部分使用IP地址(这种形式是可以的)。还是那句话,它足以验证大部分的电子邮件地址,所以还是可以拿来一用的。HTML注释:
<!-{2,}.*?-{2,}>
<!-{2,}
匹配HTML
注释的开始标签,也就是<!
后面紧跟着两个或更多个连字符的情况。.*?
匹配HTML
注释的文字部分(这里用的是懒惰型量词)。-{2,}>
匹配HTML
注释的结束标签。JavaScript注释
\/\/.*
十二、元字符总结
转义符。例如序列 \\
匹配\
而\(
则匹配(
。匹配输入字符串的开始位置。 ^<xml?
表示必须配<xml? >
开头的内容,abc <xml?>
这种不能匹配匹配输入字符串的结束位置。 </xml>$
表示必须配</xml>
结束的内容,</xml> abc
这种不能匹配匹配前面的子表达式零次或多次。例如, zo*
能匹配z
以及zoo
。* 等价于{0,}
。匹配前面的子表达式一次或多次。例如, zo+
能匹配zo
以及zoo
,但不能匹配z
。+ 等价于 {1,}
。匹配前面的子表达式零次或一次。例如, do(es)?
可以匹配do
或does
。? 等价于 {0,1}
。n
是一个非负整数。匹配确定的n
次。例如,o{2}
不能匹配Bob
中的o
,但是能匹配food
中的两个o
。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、\r
)之外的任何单个字符。要匹配包括\n
在内的任何字符,请使用像(.|\n)
的模式。匹配一个数字字符。等价于 [0-9]
。匹配一个非数字字符。等价于 [^0-9]
。匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]
。匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
。匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
。匹配非字母、数字、下划线。等价于 [^A-Za-z0-9_]
匹配 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
范围内的任意字符。匹配一个单词边界,也就是指单词和空格间的位置。例如, er\b
可以匹配never
中的er
,但不能匹配verb
中的er
。简单说\b
匹配的是字符之间的一个位置:一边是单词(能够被\w
匹配的字母数字字符和下划线),另一边是其他内容(能够被\W
匹配的字符)。匹配非单词边界。 er\B
能匹配verb
中的er
,但不能匹配never
中的er
。(pattern)
子表达式,例如, (ab)+
匹配连续出现的字符串abab
,并将其捕获(?:pattern)
匹配 patter
但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或
字符 (|
) 来组合一个模式的各个部分是很有用。例如,industr(?:y|ies)
就是一个比industry|industries
更简略的表达式。(?=pattern)
正向肯定预查( ahead asser
)向前查看,在任何匹配pattern
的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,Windows(?=95|98|NT|2000)
能匹配Windows2000
中的Windows
,但不能匹配Windows3.1
中的Windows
。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。(?!pattern)
正向否定预查( asser
),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如Windows(?!95|98|NT|2000)
能匹配Windows3.1
中的Windows
,但不能匹配Windows2000
中的Windows
。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。(?<=pattern)
反向( behin
)肯定预查向后查看,与正向肯定预查类似,只是方向相反。例如,(?<=95|98|NT|2000)Windows
能匹配2000Windows
中的Windows
,但不能匹配3.1Windows
中的Windows
。