正则表达式详解

一、表示数量的元字符

表示数量的元字符,也叫“限定符”,也叫“量词”。假设匹配个数为 x,那么下面不同的量词的匹配个数可以通过数学表达式列出。

元字符匹配个数(用 x 表示)意义
*x ≥ 0表示任意数量(包括 0);表示任意个;表示匹配其前面的字符任意个数;表示匹配任意个其前面的字符或者子表达式所代指的字符;表示前面的子表达式出现任意次数(包括 0 次)将会被匹配到;表示之前的字符连续出现任意次数(包括 0 次)将会被匹配到。 [abc]* 表示匹配 ≥0 个 由中括号所包含(指定)的(任意)字符所组成的字符串;例如,e*f 表示 f 前面的 e 出现任意次将会被匹配;zo* 能匹配 zzo zoo 等。其实就是代表一个大于等于 0 的数量集,所以 * 等价于 {0,}。(匹配优先)
+x ≥ 1表示 1 个或多个;匹配前面的子表达式(代指的字符)一次或多次;表示匹配前面的字符 1 个或者多个;表示匹配其前面的字符至少 1 次,也就是前面的字符必须有至少一个。例如,zo+ 能匹配 zozoo 等,但不能匹配 z。比如 abc+ 表示 ab 后面 c 至少要出现 1 次才会被匹配到。其实就是代表一个大于等于 1 的数量集,所以 + 等价于 {1,}。(匹配优先)
?x = 0 or x = 1表示 0 个或 1 个;匹配前面的子表达式零次或一次,也就是前面的子表达式可有可无。例如,do(es)?可以匹配 dodoes。其实 ? 等价于{0,1}。(匹配优先)
{n,m}n ≤ x ≤ m表示 n 个到 m 个,nm 均为非负整数,其中 n ≤ m。最少匹配前面的字符 n 次且最多匹配 m 次。例如,o{1,3} 会匹配 fooooood 中的前3 个 o 和后 3 o,完整的正则式成功匹配 1 次后,会继续匹配后面的字符,知道字符串末尾为止,所以会匹配出两个字符串。o{0,1} 等价于 o?。(匹配优先)
{n,}x ≥ n表示 n 个到无限个,表示匹配前面的字符至少 n 个。n 是一个非负整数,即最少 n 个,其实就是一个大于等于 n 的整数集。例如,o{2,} 表示匹配至少 2 个字符 o,所以不能匹配 Bob 中的 o,但能匹配 foooood 中的所有 oo{1,} 等价于 o+o{0,} 则等价于 o*。(匹配优先)
{,m}0 ≤ x ≤ m表示 0 个到 m 个,即最多 m 个(匹配优先)
{n}x = n表示 n 个,n 是非负整数,表示前面的子表达式必须出现 n 次;表示前面的字符连续出现 n 次,将会被匹配到。比如 a{2} 表示 a 连续出现两次就会被匹到,即匹配 aa
{n,m}?n ≤ x ≤ m表示 n 个到 m 个(忽略优先)
*?x ≥ 0表示任意数量(忽略优先)
+?x ≥ 1表示 1 个或多个(忽略优先)
??x = 0 or x = 1表示 0 个或 1 个(忽略优先)

注:“匹配优先”表示在整个正则式匹配成功的前提下,尽可能匹配更多的字符;“忽略优先”表示在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好,具体可以参见《正则表达式的贪婪模式、非贪婪模式、占有模式》。

二、表示字符的元字符

字符集

又叫字符组,[] 表示指定范围内的任意单个字符,用来匹配所包含字符中的任意一个字符或者所限定范围内的字符中的任意一个字符。

集合表达式说明
[abc]匹配方括号中的任意一个字符;匹配 ab,c 中的任意一个,例如,[abc] 可以匹配 plain 中的 a

特别注意:

  1. 如果中括号中包含元字符,则元字符降级为普通字符,不再具有元字符的功能,例如, [+.?] 匹配加号或者点号或者问号;
  2. 例如,[\d] 这不是匹配任意一个数字,而是只匹配字符 d\d 本是一个元字符,放在 [] 内就会变成普通字符 d,如果写成 \\d,则表示匹配 \d,换句话说如果要使用 \d 元字符的含义,不能放在方括号 [] 中;
  3. 例如,[\w] 不是匹配英文字母、阿拉伯数字、下划线中的任意一个字符,仅匹配 \ 或字符 w,居然和 [\d] 的效果不同,醉了

取反字符集合

又叫排除性字符组,[^] 表示匹配未被方括号包含的任意一个字符。^ 这个字符放在方括号内表示非、取反的意思;^ 放在外面则表示被匹配的字符串的头部,可以看『表示位置的元字符』小节。

集合表达式说明
[^abc]表示匹配除方括号内字符以外的任意一个字符,[^abc] 可以匹配“plain”中的plin

字符范围

方括号 [] 表达式,意为匹配括号中所限定的字符集中的任意一个字符。
[n-m] 匹配指定范围内的任意一个字符,- 表示字符范围。
[^n-m] 取反字符范围。匹配不在指定范围内的任意一个字符。

表达式说明
[a-z]表示任意一个小写英文字母;匹配 az 范围内的小写英文字母中任意一个字符
[^a-z]匹配不在 az 范围内的任意一个字符;匹配除了 a 到 z 范围的小写英文字母以外的任意一个字符;匹配除了小写英文字母以外的任意一个字符
[a-z0-9]匹配小写英文字母和阿拉伯数字中的任意一个字符
[0-9]匹配任意一个阿拉伯数字

单词边界表达式

表达式说明
\b匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”

控制符

表达式说明
\cx匹配由 x 指明的控制字符。例如,\cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Za-z 之一。否则,将 c 被视为一个普通字符
\f匹配一个换页符。等价于 \x0c\cL
\n匹配一个换行符。等价于 \x0a\cJ
\r匹配一个回车符。等价于 \x0d\cM
\s匹配任意一个空白字符。包括空格、制表符、换页符等等。等价于 [\f\n\r\t\v]
\t匹配一个制表符。等价于 \x09\cI
\v匹配一个垂直制表符。等价于 \x0b\cK

其它代表字符的元字符

表达式说明
.匹配除 \n 之外的任意一个字符。要匹配包括 \n 在内的任何字符,可以使用模式 (.|\n),表示除了换行符以外的任意一个字符或者换行符,换言之就是匹配任意一个字符了
\w\W匹配任意一个字符
\d\D匹配任意一个字符
\s\S匹配任意一个字符
.*表示匹配出现任意次的任意单个字符(除了换行符),换句话说就是匹配任意长度的任意字符。例如,a.* 表示 a 字母后面存在任意长度的任意字符都可以被匹配到。.* 可以这样理解,* 是限定符,表示其所限定的元素(本例表达式的点符号 . 就是 * 所限定的元素)可以出现任意次(≥ 0)
\a匹配任意一个英文字母,等同于 [a-zA-Z]
\l匹配任意一个小写英文字母,等同于 [a-z]
\L匹配除了小写英文字母以外的任意一个字符,等同于 [^a-z]
\u匹配任意一个大写英文字母,等同于 [A-Z]
\U匹配除了大写英文字母以外的任意一个字符,等同于 [^A-Z]
\d匹配任意一个阿拉伯数字,等价于 [0-9]
\D匹配除阿拉伯数字之外的任意一个字符,等同于 [^0-9]
\S匹配非空白字符的任意一个字符。等价于 [^\f\n\r\t\v]
\w匹配英文字母、阿拉伯数字、下划线中的任意一个字符,等同于 [0-9A-Za-z_]
\W匹配英文字母、阿拉伯数字、下划线之外的任意一个字符,等同于 [^0-9A-Za-z_]
\xn匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,\x41 匹配 A 。正则表达式中可以使用 ASCII 编码。
\n标识一个八进制转义值或一个向后引用。如果 \n 前面存在 n 对应的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字(0-7),则 n 为一个八进制转义值。例如,(.)\1 匹配两个连续的相同字符。. 匹配任意一个字符(除了换行符),而 \1 则引用前面第 1 个小括号的内容,也就是引用 (.) ,所以 . 匹配什么,就引用什么,例如,. 匹配到字符 A,那么就要引用 A,合起来就是 AA
\un匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如,\u00A9 匹配版权符号 ©
x|y匹配 xy。例如,z|food 能匹配 zfood(z|f)ood 则匹配 zoodfood
\x匹配任意一个十六进制数字,等同于 [0-9A-Fa-f]
\X匹配除了十六进制数字以外的任意一个字符,等同于 [^0-9A-Fa-f]

POSIX 字符集

表达式说明
[[:alpha:]]Alphabetic characters: [:lower:] and [:upper:] 表示匹配任意一个英文字母(不区分大小写)。等价于 \a。比如 a[[:alpha:]]{3} 表示只有 a 字母后面跟随任意 3 个英文字母的字符串才会被匹配到。
[[:lower:]]Lower-case letters 表示匹配任意一个小写英文字母。等价于 \l。比如 a[[:lower:]]{3} 表示只有当 a 后面跟着任意 3 个小写英文字母时,才会被匹配到。
[[:alnum:]]Alphanumeric characters: [:alpha:] and [:digit:] 表示匹配任意一个数字或英文字母。等价于 [a-zA-Z0-9]。比如 a[[:alnum:]]{3} 表示当 a 后面的 3 个字符为数字或字母时会被匹配到。
[[:upper:]]Upper-case letters. 表示匹配任意一个大写英文字母。比如 a[[:upper:]]{3} 表示只有当 a 后面的 3 个字符均为大写英文字母时,才会被匹配到
[[:digit:]]表示匹配任意一个数字。等价于 \d。比如 a[[:digit:]]{3} 表示只有当 a 后面的 3 个字符均为数字时,才会被匹配到。
[[:space:]]Space characters: tab, newline, vertical tab, form feed, carriage return, and space.表示匹配任意一个空白字符,即匹配制表符、换行符、垂直制表符、换页符、回车符和空格中的任意一个字符
[[:blank:]]Blank characters: space and tab.表示匹配空格或制表符
[[:cntrl:]]Control characters. In ASCII, these characters have octal codes 000 through 037, and 177 (‘DEL’). In other character sets, these are the equivalent characters, if any. 代表键盘上面的控制按键。匹配任意一个控制字符。在 ASCII 中,这些字符有八进制代码 000 到 037 和 177(‘DEL’)。在其他字符集中,这些字符是等价的字符(如果有的话)。
[[:graph:]]Graphical characters: [:alnum:] and [:punct:]. 除了空白字符(空白键和 Tab 键)以外的其它所有按键,也就是匹配任意一个图形字符;英文字母、数字、标点符号中的任意一个字符
[[:print:]]Printable characters: [:alnum:], [:punct:], and space. 匹配任意一个可打印字符,即数字、英文字母、标点符号、空格字符中的任意一个字符
[[:punct:]]Punctuation characters: ! " # $ % & ’ ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` ~ 表示匹配任意一个标点符号
[[:xdigit:]]Hexadecimal digits. 匹配任意一个十六进制数字

我们可以把 [[:alpha:]] 拆成两部分理解:
第一部分:最外层的 [ ],表示指定范围内的任意单个字符;
第二部分:最内层的 [:alpha:],表示不区分大小写的字母。

注:Java、PHP 等开发语言支持 POSIX 字符集,JS 不支持。

三、修饰符

javaScript 中正则表达式的修饰符:

修饰符说明
g全文查找
i忽略大小写查找
m多行查找

四、环视/预查/零宽断言

“环视”已经是够奇葩的叫法,又 TMD 的叫“零宽断言”,又叫“预查”。

由于匹配是零宽度的,故最终匹配到的只是一个位置。

环视按照方向划分,有顺序和逆序两种(也叫前瞻和后瞻),按照是否匹配有肯定和否定两种,组合起来便有 4 种环视。

顺序、前瞻就是向前,向左边查找;逆序、后瞻就是向后,向右边查找。

总结:所谓环视,就是根据 pattern,也就是子表达式匹配到一个位置,然后根据前后方向匹配另外一个子表达式而已。

对于这些逆天的名称我真的是醉了,摆明了得到阿里系的真传!!!😂

表达式说明
(?=pattern)正向肯定预查,又叫“顺序肯定环视”,又叫“前瞻肯定环视”,在任何匹配 pattern 的字符串开始处正向(往左边方向,向前面)匹配查找字符串,这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,Windows(?=95|98|NT|2000) 能匹配 Windows2000 中的Windows,但不能匹配 Windows3.1 中的 Windows。预查不消耗字符
(?!pattern)正向否定预查,又叫“顺序否定环视”,又叫“前瞻否定环视”,在任何不匹配 pattern 的字符串开始处正向匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,Windows(?!95|98|NT|2000) 能匹配 Windows3.1 中的 Windows,但不能匹配 Windows2000 中的 Windows。预查不消耗字符
(?<=pattern)反向肯定预查,又叫“逆序肯定环视”,在任何匹配 pattern 的字符串结尾处反向(往右边方向,向后面)匹配查找字符串。例如,(?<=95|98|NT|2000)Windows 能匹配 2000Windows 中的 Windows,但不能匹配 3.1Windows 中的 Windows
(?<!pattern)反向否定预查,又叫“逆序否定环视”,在任何不匹配 pattern 的字符串结尾处反向(往右边方向,向后面)匹配查找字符串。例如,(?<!95|98|NT|2000)Windows 能匹配 3.1Windows 中的 Windows,但不能匹配 2000Windows 中的 Windows

目前 javaScript 只支持“顺序肯定环视”和“顺序否定环视”,我们就使用 js 演示下这两种环视的具体用法,另外两种环视我使用 python 为大家演示下。

(一)顺序肯定环视

var str = "123abc789",s;
//没有使用环视,abc直接被替换
s = str.replace(/abc/,456);
console.log(s); //123456789

//使用了顺序肯定环视,匹配到了后面跟着 abc的3,把3替换成3456
s = str.replace(/3(?=abc)/,3456);
console.log(s); //123456abc789

以上的 js 代码,使用了顺序肯定环视,先在 123abc789 中匹配 abc,然后根据方向确定真正需要匹配出来的字符串的开始查找的位置,方向是顺序,也就是向前,那么就从 abc 头部开始向前查找 3,找到后替换为 3456,所以替换后的结果是:123456abc789

其实正则式3(?=abc) 可以这样来理解,要查找后面跟着 abc3,要匹配(捕获)3 ,但是这个 3 后面必须跟着 abc

(二)顺序否定环视

var str = "123abc789",s;
// 使用了顺序否定环视,由于3后面跟着abc,不满意条件,故匹配失败,所以原字符串没有被替换
s = str.replace(/3(?!abc)/,3456);
console.log(s); //123abc789

正则式 3(?!abc) 可以这样理解,查找后面没有跟着 abc3,要匹配(捕获)3,但是这个 3 后面不能跟着 abc

(三)逆序肯定环视

import re
data = "123abc789"
# 使用了逆序肯定环视,替换左边为123的连续的小写英文字母,匹配成功,故abc被替换为456
regExp = r"(?<=123)[a-z]+"
replaceString = "456"
print re.sub(regExp,replaceString,data) # 123456789

正则式 (?<=123)[a-z]+ ,可以这样来理解,匹配前面跟着 123 的满足 [a-z]+ 的字符串;匹配跟在 123 后面的符合正则式 [a-z]+ 的字符串;而这个正则式 [a-z]+ 匹配的就是 1个或者多个小写英文字母。

需要注意的是: python 和 perl 语言中的 逆序环视 的子表达式只能使用定长的文本. 比如将上述 (?<=123) 写成 (?<=[0-9]+),python 解释器将会报错: “error: look-behind requires fixed-width pattern”。

(四)逆序否定环视

import re
data = "123abc789"
# 使用了逆序否定环视,由于英文字母左侧不能为123,故子表达式[a-z]+捕获到bc,最终bc被替换为456
regExp = r"(?<!123)[a-z]+"
replaceString = "456"
print re.sub(regExp,replaceString,data) # 123a456789

正则式 (?<!123)[a-z]+ 可以这样来理解,匹配前面没有跟着 123 的小写英文字母,这个明显是贪婪模式,bc 都满足正则式,但是却一次性匹配了 bc

五、非捕获性分组

表达式说明
(?:pattern)匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用“或”字符 | 来组合一个模式的各个部分是很有用。例如,industr(?:y|ies) 就是一个比 industry|industries 更简略的表达式,白痴,这会更简略吗?是不是对简略这个词有什么误解??

六、替换变量

在正规表达式中使用小括号 () 包裹起来的内容,可在后面使用 \1\2 等变量来访问对应位置的 () 中的内容。

七、转义符号

反斜杠 \ 看作为转义符号,告诉正则表达式引擎反斜杠 \ 的下一个字符是一个特殊字符或一个原义字符。

例如,n 匹配字符 n\n 匹配一个换行符;\\ 匹配 \,因为 \ 在默认情况下是个特殊字符,要表示成原义字符就要加上 \ 进行转义;同理\( 则匹配 (

八、表示位置的元字符

字符描述
^表示锚定行首,匹配输入字符串的头部位置,这是个虚拟的概念,表示字符串的第 1 个字符的前面。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 \n\r 之后的位置。比如 ^hello 表示只匹配位于行首的 hello 字符串
$表示锚定行尾,匹配输入字符串的尾部位置,这是个虚拟的概念,表示字符串的最后 1 个字符的后面。如果设置了 RegExp 对象的 Multiline 属性,$ 也匹配 \n\r 之前的位置。比如 hello$ 表示只匹配位于行尾的 hello 字符串
^.+$匹配所有不含有换行符 \n 的行。^$ 分别代表锚定行首与锚定行尾,那么,我们将它们结合在一起使用,表示之间的内容既位于行首又位于行尾,例如,^Linux$ 表示 Linux 既位于行首,同时也位于行尾,换句话说,就是整行中只有一个单词 Linux。
^$^$ 表示行首与行尾相连,换句话说就是空行
\<\< 表示锚定词首。匹配单词的头部。例如,\<lao 表示以lao作为词首的单词将会被匹配到
\>\> 表示锚定词尾。匹配单词的尾部。例如,liang\> 表示以liang作为词尾的单词将会被匹配到
\<am我们也可以将”<“与”>”结合在一起使用,\<am\> 表示当 am 既位于词首又位于词尾时则会被匹配到,换句话说,就是当 am 作为一个独立的单词时,则会被匹配到
\b我们还可以使用 \b 去代替 \<\>\b 既能锚定词首,也能锚定词尾。例如,\blao 匹配以 lao 为词首(lao 位于词首位置)的单词;lao\b 匹配以 lao 为词尾的单词;\bLinux\b 匹配以 Linux 为词首又以 Linux 为词尾的单词,也就是匹配 Linux 这个单词
\B\B”则与”\b”正好相反,”\B”是用来匹配非单词边界的,如下”\BLinux”表示只要 Linux 不是词首(即不位于词首)就会被匹配到,换句话说就是匹配所有不以 Linux 为词首的单词;同理 Linux\B 表示只要 Linux 不是词尾就会被匹配到

九、特别说明

匹配字符失败会尝试匹配位置

正则表达式和源字符串匹配时,从 0 位置开始匹配的,字符匹配失败后会尝试匹配位置,所以你会发现下面的奇葩的现象。

使用正则表达式 [a-z]* 去匹配字符串 fdff234df!@kf ,匹配成功的地方替换成 ?,最后得到的结果是:??2?3?4??!?@??,正常我们能想象到的结果应该是:?234?!@?,因为按理可以匹配到字符串 fdffdfkf,所以应该是这 3 个地方替换成 ? 才合理嘛。

正如上述所讲的,字符匹配失败后会尝试匹配位置,所以上述的匹配过程如下:

在这里插入图片描述

[a-z]* 获得控制权,从位置 0 开始匹配,先匹配字符 f,匹配成功(匹配成功就不尝试匹配位置,而是直接匹配下个字符),接着匹配字符 d,也匹配成功,直到匹配字符 2 时匹配失败了(这时不会去匹配 2 的位置,因为前面有回溯的位置,会把控制权移交出去),这时候会进行回溯,并且保存匹配成功的字符,然后把控制权交给下一个子表达式,可是没有下个表达式了,所以整个正则表达式第一次迭代匹配结束,最终成功匹配到字符 fdff,匹配开始位置 0,结束位置 4

接着开始第 2 次迭代匹配,从位置 4 开始匹配,先匹配字符 2,匹配失败,尝试匹配位置 4,因为正则表达式允许空的情况,所以匹配成功,保存匹配成功的位置 4,同时记录下回溯的位置 4,结束第 2 次迭代匹配。再接着开始第 3 次迭代匹配,从位置 5 开始,重复以上的过程,直到整个字符串结尾,结束迭代匹配。

所以你会发现,在第 3 次迭代之前,已经匹配成功了两个地方,第 1 此迭代匹配到了字符串 fdff,第 2 次迭代匹配到了位置 4,所以替换成问号 ? 后,你会看到 2 的前面有 2 个问号 ?,字符串的尾部没有字符了,所以匹配失败,尝试匹配位置,结果也是匹配上了,这个位置因此也会替换成问号 ?

匹配单词边界位置

Word boundary . Matches a word boundary position between a word character and non-word character or position ( start / end of string )

元字符 \b 用来匹配单词边界位置。所谓单词边界位置是指单词字符和非单词字符或者位置(字符串的开始/结束)之间的边界位置,看下面的例子:

使用 \b 去匹配字符串 fdff234df!@kf aa<p>one</p>bb<div>two</div>cc 然后替换成问号 ?,会得到结果:?fdff234df?!@?kf? ?aa?<?p?>?one?</?p?>?bb?<?div?>?two?</?div?>?cc?

看到没有,第一个 f 和字符串的开始位置(start of string)之间有边界,所以替换成了 ?,第四个 f 和非单词字符 ! 之间也存在边界,所以也会替换成功 ?,非单词字符 @ 和单词字符 k 之间存在边界,所以也替换成功了 ?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值