《正则表达式30分钟入门教程》笔记
寒假时做的笔记,现转发到这里。
原文请见此处
本文介绍的是 .Net Framework 4.x 下的正则表达式,但大部分语法可以通用。
入门
假设你在一篇英文小说里查找hi,你可以使用正则表达式hi
。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配 hi,HI,Hi,hI 这四种情况中的任意一种。
但hi
这个表达式会匹配到包含hi的其他单词,如 him, history, high 等等。如果只想匹配 hi 这个词,需要使用\bhi\b
。
\b
匹配这样的位置:它的前一个字符和后一个字符不全是\w
。\b
并不表示单词分隔符,尽管看起来的效果确实如此。
\b
是一个元字符(metacharacter)。
*
也是元字符,它代表的是数量——它指定*
前边的内容可以连续重复使用任意次。
.
也是元字符,匹配除了换行符'\n'
以外的任意字符。
于是,.*
就表示任意数量的非换行符。
假如要匹配hi后面不远处跟着一个Lucy,则可用\bhi\b.*Lucy
。
元字符\d
匹配一位数字(0-9)。如\d\d\d\d
表示一个4位的数字。为了简洁,上面的表达式可以简写为\d{4}
,表示\d
要连续匹配4次。而\d{5,12}
表示重复的次数不能少于5次,且不能多于12次。
元字符
对中文的特殊处理是由 .Net 正则表达式引擎支持的,不是标准内容。
\s
匹配 任意的空白符。这里的空白符指空格,Tab,'\n'
和中文全角空格等。
\w
匹配 字母或数字或下划线或汉字。
+
与*
类似,不同的是*
允许重复任意次(包括0次),而+
允许重复1次或更多次。
^
匹配 字符串的开始。它匹配位置,下面的$
也一样。
$
匹配 字符串的结束。
上面两个元字符常常用于验证输入的内容。比如匹配一个5-12位的QQ号,可用^\d{5,12}$
。
一些例子:
\ba\w*\b
匹配以a开头的单词。这里的单词是指不少于一个的连续的\w
。
\d+
匹配1个或更多连续的数字。
\b\w{6}\b
匹配刚好6个字符的单词。
字符转义
要查找\
这个字符本身,应使用\\
。
要查找.
这个字符本身,应使用\.
。
其他类同。
重复
正则表达式中所有的限定符(即用于指定数量的代码):
代码/语法 | 说明 |
---|---|
* |
重复零次或更多次 |
+ |
重复一次或更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
字符类
想要匹配数字、空白等的集合是容易的,因为已经有对应的元字符。如果想要匹配字母的集合,可以在方括号里列出所有备选的字母。如[aeiou]
匹配任何一个英文元音字母,[.?!]
匹配三种标点中的任何一个。
也可以指定字符范围,如[0-9]
等价于\d
,而在纯英文场景下,[a-z0-9A-Z]
等价于\w
。
注意[]
中的字符不能用空格分隔。如果用了空格,则空格也会被匹配。
分枝条件
正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|
把不同的规则分隔开。
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位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。
使用分枝条件时,要注意各个条件的顺序。例如\d{5}-\d{4}|\d{5}
和\d{5}|\d{5}-\d{4}
匹配的是不同的模式,因为分枝条件总是从左向右匹配,后者的前一个条件会涵盖后一个条件的全部情况。
分组
表示重复的单个字符是容易的:只需要在该字符后面加限定符即可;如果想要表示重复的多个字符,则需要用小括号指定子表达式(又名 分组)。
(\d{1,3}\.){3}\d{1,3}
是一个简单的IPv4匹配表达式。注意IPv4允许前导0。这个表达式有个缺陷,就是它会匹配类似256.300.888.999
这种不合法的地址。
标准的Ipv4匹配表达式是((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 这几个字母以外的任意字符 |
后向引用
使用小括号指定一个分组后,每个分组会自动拥有一个组号,规则如下:
- 分组0对应整个正则表达式
- 从左向右扫描整个表达式,以分组的左括号为准,第一个分组为1,第二个分组为2,依此类推。这样的扫描进行两遍,第一遍只给未命名组分配,第二遍只给命名组分配。
- 可以使用
(?:exp)
这样的语法来剥夺一个分组对组号分配的参与权。
后向引用用于重复搜索前面某个分组匹配到的文本。如\1
表示分组1匹配到的文本。
例子:
表达式\b(\w+)\b\s+\1\b
用来匹配重复的单词,如go go或kitty kitty。其中\b(\w+)\b
表示一个单词,这个单词被捕获到编号为1的分组中。然后跟1个或更多空白字符(\s+
),最后还是分组1捕获到的内容(\1
)。
也可以手动指定子表达式的组名。语法为(?<Word>\w+)
,或将尖括号换成'
:(?'Word'\w+)
。这样这个分组的组名就变成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)
又名零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp
。比如\b\w+(?=ing\b)
匹配以ing
结尾的单词的前面部分。如对字符串I'm singing while you are dancing
应用该表达式,将匹配sing
和danc
。
(?<=exp)
又名零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式。比如(?<=\bre)\w+\b
会匹配以re
开头的单词的后半部分。
负向零宽断言
零宽度负预测先行断言(?!exp)
断言此位置的后面不能匹配表达式exp
。例如\d{3}(?!)
匹配三位数字,且这三位数字的后面不能是数字。
零宽度负回顾后发断言(?<!exp)
断言此位置的前面不能匹配表达式exp
。例如(?<![a-z])\d{7}
匹配前面不是小写字母的7位数字。
一个更加复杂的例子:表达式(?<=<(\w+)>).*(?=<\/\1>)
匹配不包含属性的简单HTML标签内里的内容。它不匹配标签本身。
注释
如果想要包含注释,最好启用“忽略模式里的空白符”选项,这样在编写表达式时可以任意的添加空格,Tab或换行,这些空格将被自动忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:
(?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的内容
# (即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
<\/\1> # 查找尖括号括起来的内容
# 查找尖括号括起来的内容
) # 后缀结束
贪婪与懒惰
正则表达式中包含能接受重复的限定符时,通常的行为是匹配尽可能多的字符。如a.*b
将会匹配最长的以a
开始,以b
结束的字符串。这称为贪婪匹配。
有时我们需要于此相反的懒惰匹配,前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?
。于是,a.*?b
就表示最短的以a
开始,以b
结束的字符串。
例如,对于字符串aabab
,a.*b
将匹配整个字符串,a.*?b
将匹配aab
和ab
。
代码/语法 | 说明 |
---|---|
*? |
重复任意次,但尽可能少重复 |
+? |
重复1次或更多次,但尽可能少重复 |
?? |
重复0次或1次,但尽可能少重复 |
{n,m}? |
重复n到m次,但尽可能少重复 |
{n,}? |
重复n次以上,但尽可能少重复 |