摘录与 《正则表达式必知必会》

零、快速上手

  • [abc] 匹配单个字符abc

  • . 匹配除换行符(\n、\r)之外的任何单个字符。

  • [0-9]等价[0123456789] 含义是匹配一个数字,输入 123。 匹配三个结果 123-(连字符)是一个特殊的元字符,它只有出现在[]之间的时候才是元字符

  • \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)?可以匹配dodoes? 等价于 {0,1}

  • x|y 匹配xy。例如,z|food能匹配zfood(z|f)ood则匹配zoodfood

  • ()表示子表达式,比如我有个内容<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标签中的内容,我们正则可以这样写(?<=<title>).*(?=</title>)

      <head>
      <title>Ben Forta's Homepage</title>
      </head>
    
    • ?=是向前查看,.+(?=:)匹配https://mail.forta.com/中的https。任何子表达式都可以转换为向前查看表达式,只要在其之前加上一个?=即可。
    • ?<=是先后查看,(?<=\$)[0-9.]+匹配ABC01: $23.45中的23.45向后查看模式则只能是固定长度。几乎所有的正则表达式实现都有此限制。

一、正则表达式用途

正则表达式语言是内置于其他语言或软件产品里的“迷你”语言。主要用户文本处理(查找替换)。

二、匹配单个字符

匹配普通文本

内容:
Hello, my name is Ben. Please visit
my website at http://www.forta.com/.

正则:Ben // 则表达式是区分字母大小写的,所以Ben不匹配ben
匹配结果:Ben

匹配任意字符

.字符(英文句号)可以匹配任意单个字符

内容:
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls

正则:sales.
匹配结果:    
sales2
sales3

匹配特殊字符(转移字符)

转义符\,比如\.表示.本身,\\表示\,\*表示*

三、匹配一组字符

匹配多个字符中的某一个

[Rr]负责匹配字母Rr[Ee]负责匹配字母Ee

内容:
The phrase "regular expression" is often
abbreviated as RegEx or regex.
正则:[Rr]eg[Ee]x
匹配结果:RegEx、regex

利用字符集合区间

[]表示一个字符的集合,这意味着表达式将匹配方括号中列出的任何一个字符。[0123456789]匹配一个数字,等价与[0-9],比如匹配数字1,不能匹配12

  • A-Z,匹配从AZ的所有大写字母。
  • a-z,匹配从az的所有小写字母。
  • A-F,匹配从A到F的所有大写字母。
  • -(连字符)是一个特殊的元字符,它只有出现在[]之间的时候才是元字符。在字符集合以外的地方,-只是一个普通字符,只能与-本身相匹配。因此,在正则表达式里,-字符不需要被转义。

[A-Za-z0-9]等价[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]

内容
body {  background-color: #fefbd8; }

正则:#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]
匹配:#fefbd8

排除

[^0-9]表示匹配的是任何不是数字的字符

内容:
sam.xls
na1.xls
na2.xls
sa1.xls


正则:[ns]a[^0-9]\.xls
匹配:sam.xls

四、使用元字符

匹配数字

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字符类

主要用于grepvim这类工具支持

字符 描述
[:alnum:]: 匹配所有的字母和数字字符。相当于 [A-Za-z0-9]
[:alpha:]: 匹配所有的字母字符。相当于 [A-Za-z]
[:digit:]: 匹配所有的数字字符。相当于 [0-9]
[:space:]: 匹配所有的空白字符,包括空格、制表符、换行符等。
[:blank:]: 匹配所有的空格和制表符字符。
[:lower:]: 匹配所有的小写字母。
[:upper:]: 匹配所有的大写字母。
[:print:]: 匹配所有可打印的字符。
[:punct:]: 匹配所有的标点字符。
[:graph:]: 匹配所有的可见字符,包括字母、数字、标点符号等。

五、重复匹配

+ 匹配一个或多个

[\w.]+匹配字母数字字符、下划线和.的一次或多次重复出现

内容:
Send personal email to ben@forta.com. For questions
about a book use support@forta.com. Feel free to send
unsolicited email to spam@forta.com (wouldn't it be
nice if it were that simple, huh?).

正则:[\w.]+@[\w.]+\.\w+
匹配:ben@forta.com support@forta.com spam@forta.com

匹配零个或多个字符

[\w.]*匹配.或字母数字字符的零次或多次重复出现

内容:
Hello hello.ben@forta.com.cn is my email address. support@forta.com

正则:\w+[\w.]*@[\w.]+\.\w+
匹配:hello.ben@forta.com.cn support@forta.com


The URL is http://www.forta.com/, to connect
securely use https://www.forta.com/ instead.
正则:http[s]*://  等价 https?:// 
匹配:http:// https://

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}mn均为非负整数,其中n <= m。最少匹配n次且最多匹配m次。例如,o{1,3}将匹配fooooood中的前三个oo{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,}? 

六、位置匹配

单词边界

\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b可以匹配never中的er,但不能匹配verb中的er。简单说\b匹配的是字符之间的一个位置:一边是单词(能够被\w匹配的字母数字字符和下划线),另一边是其他内容(能够被\W匹配的字符)。

内容:
The cat scattered his food all over the room.

正则:\bcat\b
匹配结果:cat

字符串边界

内容:
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.*\?> 开头的才行。

多行模式

许多正则表达式都支持使用一些特殊的元字符去改变另外一些元字符的行为,(?m)就是其中之一,它可用于启用多行模式(multiline mode)多行模式迫使正则表达式引擎将换行符视为字符串分隔符

内容:
<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”

包括JavaScript在内的许多正则表达式实现都不支持(?m)

七、使用子表达式

使用子表达式进行分组

(&nbsp;)是一个子表达式,它被视为单一的实体。因此,紧随其后的{2,}将作用于整个子表达式(而不仅仅是分号)

内容:
Hello, my name is Ben&nbsp;Forta, and I am
the author of multiple books on SQL (including
MySQL, Oracle PL/SQL, and SQL Server T-SQL),
Regular&nbsp;&nbsp;Expressions, and other subjects.

正则:(&nbsp;){2,}
匹配结果:&nbsp;&nbsp;    


内容
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

八、反向引用

反向引用匹配

内容
<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)可以用来代表整个正则表达式。
  • 一些比较新的正则表达式实现还支持“命名捕获”(named capture):给某个子表达式起一个唯一的名称,随后用该名称(而不是相对位置)来引用这个子表达式。目前很多语音还没支持

替换操作

内容
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-555

大小写转换

内容
<body>
    <h1>Welcome to my Homepage</h1>
</body>

正则:(\d{3})-(\d{3})-(\d{4})
替换正则:$1\U$2\E$3 (VScode 替换可以不用加\E)

替换结果:
<body>
    <h1>WELCOME TO MY HOMEPAGE</h1>
</body>
元字符 说明
\E 结束\L\u转换
\l 把下一个字符转换为小写
\L \L\E之间的字符全部转换为小写
\u 把下一个字符转换为大写
\U \u\E之间的字符全部转换为大写

九、环视

<head>
<title>Ben Forta's Homepage</title>
</head>

正则:<title>.*</title>
匹配结果:<title>Ben Forta's Homepage</title>

明知是自己不需要的东西,还把它们检索出来,然后再手动删除,这种做法毫无意义。你真正需要的是想办法构造出一种模式,该模式中包含一些不用被返回的匹配——这些匹配是为了找出正确的匹配位置,其自身不属于最终的匹配结果。换句话说,你需要进行“环视”。

<head>
<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)也叫零宽度正回顾后发断言。

十、嵌入式条件

反向引用条件

反向引用条件仅在一个前面的子表达式得以匹配的情况下才允许使用另一个表达式。比如一个字符串(123),左右括号需要同时出现才行

内容
<div>
<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>
</div>

正则:(<[Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*<\/[Aa]>)

(<[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行?因为左括号(没有与之匹配的右括号),所以嵌入条件被视为无关文本,完全被忽略了。

并非所有的正则表达式实现都支持条件处理。

十一、常用正则表达式

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]))

URL:

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]+

决定电子邮件地址格式有效性的规则极其复杂。该模式无法验证所有可能的电子邮件地址。比如说,这个模式会认为ben..forta@forta.com是有效的(显然无效),也不允许主机名部分使用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)?可以匹配dodoes? 等价于 {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} mn均为非负整数,其中n <= m。最少匹配n次且最多匹配m次。例如,o{1,3}将匹配fooooood中的前三个oo{0,1}等价于o?。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串ooooo+?将匹配单个o,而o+将匹配所有o
. 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括\n在内的任何字符,请使用像(.|\n)的模式。
\d 匹配一个数字字符。等价于 [0-9]
\D 匹配一个非数字字符。等价于 [^0-9]
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
\w 匹配字母、数字、下划线。等价于[A-Za-z0-9_]
\W 匹配非字母、数字、下划线。等价于[^A-Za-z0-9_]
x|y 匹配xy。例如,z|food能匹配zfood(z|f)ood则匹配zoodfood
[xyz] 字符集合。匹配所包含的任意一个字符。例如,[abc]可以匹配plain中的a
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,[^abc]可以匹配plain中的plin
[a-z] 字符范围。匹配指定范围内的任意字符。例如,[a-z]可以匹配az范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,[^a-z]可以匹配任何不在az范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b可以匹配never中的er,但不能匹配verb中的er。简单说\b匹配的是字符之间的一个位置:一边是单词(能够被\w匹配的字母数字字符和下划线),另一边是其他内容(能够被\W匹配的字符)。
\B 匹配非单词边界。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
(?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如(?<!95|98|NT|2000)Windows能匹配3.1Windows中的Windows,但不能匹配2000Windows中的Windows