《正则表达式必知必会》读书笔记


人民邮电出版社出版的《正则表达式必知必会》一书适合正则表达式快速入门,我花了一点时间阅读完这本书,然后总结了一下画了一张思维导图。如下所示,这张思维导图主要是对全书架构做一个呈现,其基本涉及到了全书所有主题,详细的规则用法在下面的博客中展开。

《正则表达式必知必会》全书思维导图


引子

正则表达式(regular expression,简称 regex)是文本处理方面最强大的工具之一。正则表达式语言用来构造正则表达式(最终构造出来的字符串就称为正则表达式),正则表达式用来完成搜索或替换操作。实际的正则表达式经常被简称为模式,它们其实是一些由字符构成的字符串。这些字符可以是普通字符(纯文本)或元字符(有特殊含义的特殊字符)。


匹配纯文本

静态文本通常指的是由 0~9、a~z、A~Z 构成的文本。匹配静态文本很简单,要匹配的文本是什么,正则表达式便是什么。如下例:

//测试文本
I am 21.

//若要匹配 I,正则表达式即为 I
//若要匹配 21,正则表达式即为 21

这里实际上有两个问题,也是匹配字符时会有的问题,见匹配单个字符部分的注意。


匹配单个字符

若要匹配的单个字符是在正则表达式里有特殊意义的字符,则需要用到转义字符“\”。如下例:

//测试文本
as/df[f520.

//若要匹配 /,正则表达式为 \/
//若要匹配 [,正则表达式为 \[
//若要匹配 .,正则表达式为 \.

正则表达式提供了一个用来匹配任意单个字符的简易方法,那就是使用符号“.”。“.”字符可以匹配任何单个的字符、字母、数字甚至是其本身。事实上,这一说法并不准确,因为在绝大多数的正则表达式实现里,“.”只能匹配出换行符以外的任何单个字符。

在同一个正则表达式里允许使用多个“.”字符,它们既可以连续出现(一个接着一个—— .. 将匹配任意两个字符),也可以间隔着出现在模式的不同位置。

注意:绝大多数正则表达式引擎的默认行为是只返回第1个匹配的结果。正则表达式是区分大小写的。

要记住,正则表达式可以用来匹配包含着字符串内容的模式。但匹配的并不总是整个字符串,而是与某个模式相匹配的字符,即使它们只是整个字符串的一部分。


匹配一组字符

元字符 [] 用来定义一个字符集合。可以匹配多个字符中的一个,如下例:

//测试文本
sales1.xls
order3.xls
na1.xla
na2.xla
sa1.xla
ca1.xls

//若要匹配 na 和 sa 开头的字符串,正则表达式为 [ns]a.\.xls

上例中,[ns]表示一个包含 n 和 s 的字符集合,它既可以匹配 s 又可以匹配 n。注意是或关系,即每次匹配只匹配字符集合里的一个字符。

还可以使用元字符 [-] 组成字符集合区间进行匹配,常见的字符区间有以下:

[0-9]   //匹配0~9这十个数字字符
[a-z]   //匹配从a到z的所有小写字母字符
[A-Z]   //匹配从A到Z的所有大写字母字符
[A-z]   
//匹配从ASCII字符A到ASCII字符z的所有字母。这个模式一般不常用,因为它还包含着 [ 和 ^ 等在ASCII字符表里排列在Z和a之间的字符

理论上来说,字符区间的首尾字符可以是ASCII字符表里的任意字符,但在实际工作中,最常用的字符区间还是数字字符区间和字母字符区间。

注意:-(连字符)是一个特殊的元字符,作为元字符它只能用在 [] 之间。在字符集以外的地方,-只是一个普通字符,只能与 - 本身相匹配。因此,在正则表达式里,- 字符一般不需要被转义。

有些时候,直接匹配所需字符可能需要考虑的情况比较多,过于麻烦;这时我们就可以使用元字符 ^ 进行取非匹配,与数学解题中先求补集再求原集的方法有异曲同工之妙。下面是一个例子:

//测试文本
sales1.xls
orders3.xla
sam.xls
na1.xls
sa1.xls

//若要匹配 sam.xls,显然地使用取非匹配更简单
//模式为 {ns]a[^0-9]\.xls

我们知道 [0-9] 将匹配 0-9 这十个数字字符,因此上例中 [^0-9] 匹配的是任何不是数字的字符。
注意:^ 的效果作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟在 ^ 字符后面的那一个字符或字符区间。另外,只有当元字符 ^ 紧跟在 [ 之后,才表示取非意义;因此,当想把 ^ 用作取非时,必须将其与“[”和“]”连用。


使用元字符

元字符是一些在正则表达式里有着特殊含义的字符。英文句号(.)是一个元字符,它可以用来匹配任何一个单个字符。类似地,左方括号([)也是一个元字符,它标志着一个字符集合的开始。因为元字符在正则表达式里有着特殊意义,所以这些字符就无法用来代表它们本身。比如说,我们不能使用一个“[”来匹配“[”本身,也不能使用“.”来匹配“.”本身。正如在匹配单个字符部分所说的那样,若要匹配元字符必须在元字符的前面加上一个反斜杠对它进行转义;例如,转义序列\.将匹配“.”本身,转义序列\[将匹配“[”本身。

下面是一个典型的例子:若我们要匹配myArray[0]、myArray[1] ... myArray[9] 那么应该构造如下的正则表达式myArray\[[0-9]\]。分析这个模式,我们可知myArray将匹配myArray\[将匹配[[0-9]将匹配数字字符,\[将匹配[,这正是我们所期望的。

注意:用来对字符进行转义的“\”字符也是一个元字符,显然地,在正则表达式里它的特殊含义是对其它字符进行转义。因此,若要匹配“\”,模式应该为\\

元字符大致可以分两种:一种是用来匹配文本的(比如“.”),另一种是正则表达式的语法所要求的(比如“[”和“]”)。下面介绍一些用来匹配空白字符的元字符:

元字符说明
[\b]回退(并删除)一个字符(键盘上BackSpace键)
\f换页符
\n换行符
\r回车符
\t制表符(Tab键)
\v垂直制表符

若我们需要匹配两行文本之间的一个空白行,在Windows系统上的模式为\r\n\r\n,在Unix和Linux系统上的模式为\n\n。原因是Windows使用 回车+换行 来结束一个文本行,而Unix和Linux只使用一个换行符来结束一个文本行。

字符集合是最常用的匹配形式,而一些常用的字符集合可以用特殊元字符来代替。这些元字符匹配的是某一类别的字符(术语称之为字符类)。类元字符并不是必不可少的东西,但是用它们构造出来的正则表达式简明易懂,在实践中很有用。下面是以些基本的字符类:

元字符说明
\d任何一个数字(等价于[0-9])
\D任何一个非数字(等价于[^0-9])
\w任何一个字母数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_])
\W任何一个非字母数字下划线字符(等价于[^a-zA-Z0-9_])
\s任何一个空白字符(等价于[\f\n\r\t\v])
\S任何一个非空白字符(等价于[^\f\n\r\t\v])

另外,在正则表达式里,十六进制数值要用前缀\x来给出,八进制数值要用前缀\0来给出(八进制数值本身可以是两位或三位数字).


重复匹配

正则表达式里有一些元字符可以简单的控制单个字符(或字符集合)的匹配次数,最基本的三个元字符如下表,在使用时,只需把它们放在要进行多次匹配的字符(或字符集合)后面即可。

元字符说明
+匹配一个字符(或字符集合)的一次或多次出现
*匹配一个字符(或字符集合)的零次或多次出现
?匹配一个字符(或字符集合)的零次或一次出现

在给一个字符集合加上+后缀的时候,必须把“+”放在这个字符集合的外面。比如说[0-9]+是正确的,[[0-9+]却不是。不过事实上[[0-9+]也是一个合法的正则表达式,但它匹配的是一个0~9的数字或加号,而不是我们想要的匹配一个或多个数字。
注意:一般来说,当在字符集合里使用的时候,像.+这样的元字符被解释为普通字符,不需要被转义。
下面是个例子:

//测试文本
http
https

//模式 \w+ 将匹配到 http、https(htpp 和 https 均为字母字符连续多次出现)
//模式 https? 将匹配到 http、https(一个s是零次,一个s是一次)
//模式 https* 将匹配到 http、https(一个s是零次,一个s是多次(一次属于多次))

上例只是为了演示元字符+ * ?的使用,实际工作中,为了同时匹配出 http 和 https,只有第二种方法
是最好的。

另外,我们还可以使用{}字符精确地控制重复次数。

使用形式说明
{m}只匹配m次
{m,}至少匹配m次,即字符必须重复m次或更多次
{m,n}为重复匹配次数设定一个区间,即最少匹配m次,最多匹配n次

上表中的m可以为0。同之前的+ * ?元字符,一样,在使用{}时,必须将要匹配的单个字符或(字符集合)置于紧挨着它的左侧。例如:

\$\d{3}     //将匹配$和连续出现的3个数字字符

?只能匹配一个字符,{m}{m,n}也有一个重复次数的上限;换句话说,这几种语法所定义的“重复次数”都是有限的。但本部分所介绍的其他重复匹配语法在重复次数方面并没有上限值,而这样做有时会导致过度匹配的现象。+ *都是所谓的“贪婪型”元字符,它们在进行匹配时的行为模式是多多益善而不是适可而止的。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到第一个匹配时为止。在不需要这种“贪婪行为”的时候,我们应该使用这些元字符的“懒惰型”版本(“懒惰”在这里的含义是匹配尽可能少的字符,与“贪婪型”元字符的行为模式刚好相反)。下表是“贪婪型”元字符的“懒惰型”版本。

贪婪型元字符懒惰型元字符
**?
++?
{m,}{m,}?

观察上表可以发现,懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个 ? 后缀即可。
下面用一个具体的例子来说明贪婪型元字符与懒惰型元字符的区别:

//测试文本
This offer is not available to customers living in <B>AK</B> and <B>HI</B>.

//若使用模式 <[Bb]>.*</[Bb]>,将匹配 <B>AK</B> and <B>HI</B>
//若使用模式 <[Bb]>.*?</[Bb]>,将匹配 <B>AK</B><B>HI</B>

位置匹配

位置匹配解决在什么地方进行字符串匹配操作的问题。这里有一个例子能让我们形象地对位置匹配及其相关概念有一个直观的认识:

//测试文本
Text cat scattered his food all over the room.

//若使用模式 cat,将匹配 cat 和 单词scatteres中的cat

上例中的情况也正是我们在第一部分匹配单个字符所说的“正则表达式可以用来匹配包含着字符串内容的模式。但匹配的并不总是整个字符串,而是与某个模式相匹配的字符,即使它们只是整个字符串的一部分”。虽然模式 cat 把测试文本中的所有“cat”都匹配出来了,但那并不是我们想要的结果,我们只想匹配单词cat。为了正确解决这个问题,我们不得不使出边界限定符,也就是在正则表达式里用一些特殊的元字符来表明我们想让匹配操作在什么位置(边界)发生。

第一种边界就是由限定符\b指定的单词边界。回到刚才那个例子:

//测试文本
Text cat scattered his food all over the room.

//若使用模式 \bcat\b,将仅仅匹配单词cat

在测试文本中,单词 cat 的前后都有一个空格,而这将与模式\bcat\b相匹配(空格是用来分隔单词的字符之一)。单词 scattered 中的字符序列 cat 不能与这个模式相匹配,因为它的前一个字符是s、后一个字符是t(这两个字符都不能与\b相匹配)。

若想表明不匹配一个单词边界(即字母数字下划线之间,或者非字母数字下划线之间),可以使用\B。下面这个例子演示了使用 \B 来查找其前后都有多余空格的连字符:

//测试文本
Please enter the nine-digit id as it appears on your  on your color - coded pass-key.

//使用模式 \B - \B,将匹配测试文本中的连字符“-”

上例中,\B - \B将匹配一个前后都不是单词边界的连字符。nine-digit 和 pass-key 中的连字符不能与之匹配,但 color - coded中的连字符可以与之匹配。

字符串边界与之类似,只不过是用来进行与字符有关的位置匹配而已(字符串的开头、字符串的结束、整个字符串等)用来定义字符串边界的元字符有两个;一个是用来定义字符串开头的^,另一个是用来定义字符串结尾的$。除了位置上的差异,^$两者的用法完全一样,一个不允许字符串开头之前有任何内容,一个不允许字符串结尾之后有任何内容。

注意:^是几个有着多种用途的元字符之一。只有当它出现在一个字符集合里(被放在[]之间)并紧跟在左方括号[的后面时,它才能发挥“求非”作用。如果是在一个字符集合的外面并位于一个模式的开头,^将匹配字符串中的开头。

事实上,^匹配一个字符串的开头,$匹配一个字符串的结尾这一说法并非绝对正确,还有一个例外或者说有一种改变这种行为的办法。有许多正则表达式都支持使用一些特殊的元字符去改变另外一些元字符行为的做法,用来启用分行匹配模式的(?m)记号就是一个能够改变其他元字符行为的元字符序列。分行匹配模式将得正则表达式引擎把行分隔符当作一个字符串分隔符来对待。在分行匹配模式下,^不仅匹配正常的字符串开头,还将匹配行分隔符(换行符)后面的开始位置(这个位置是不可见的);类似地,$不仅匹配正常的字符串结尾,还将匹配行分隔符(换行符)后面的结束位置。

在使用时,(?m)必须出现在整个模式的最前面,就像下面这个例子里那样。在这个例子里,我们将使用一个正则表达式把一段JavaScript代码里的注释全部查找出来:

//测试文本
    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;
    }

//若使用模式 ^\s*//.*$,将仅匹配第一条注释
//若使用模式 (?m)^\s*//.*$,将匹配所有注释

^\s*//.*$将匹配一个字符串的开始,然后是任意多个空白字符,再后面是//,再往后是任意文本,最后是一个字符串的开始。不过,这个模式只能找出第一条注释(并认为这条注释将一直延续到文件末尾,因为*是一个“贪婪型”元字符)。加上(?m)前缀之后,(?m)^\s*//.*$将把换行符视为一个字符串分隔符,这样就可以把每一行注释都匹配出来了。
警告:有许多正则表达式实现不支持(?m)


使用子表达式

子表达式是一个更大的表达式的一部分,把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用 () 括起来。下面是一个查找IP地址的例子:

//测试文本
Pinging hog.forta.com {12.159.46.200} with 32 bytes of data

//若使用模式 \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3},将匹配出 12.159.46.200

\d{1,3}在这个模式里重复了4次,它们分别匹配 IP 地址里的一组数字。IP 地址里的4组数字由.分隔,该字符由模式里的转义序列\.负责匹配。在这个例子里,模式\d{1,3}\.(最多3个数字字符,后面跟着一个.)连续出现了3次,它同样可以被表达为一个重复。下面是这个例子的另一种解决方案:

//测试文本
Pinging hog.forta.com {12.159.46.200} with 32 bytes of data

//若使用模式 (\d{1,3}\.){3}\d{1,3},也可匹配出12.159.16.200

这个模式与前面那一个有着同样的效果,但我们这次使用了另一种语法:先用 () 把表达式\d{1,3}\.括起来使他成为一个子表达式,再用(\d{1,3}\.){3}把这个子表达式重复了3次,最后面的\d{1,3}用来匹配 IP 地址里的最后一组数字。

正则表达式中控制匹配重复次数的元字符仅作用于它前面的单个字符(紧挨着它),如abalj{3}{3}只匹配字母 j 重复3次;而 () 使得括起来的模式成为一个整体(我们称之为子表达式),然后就可以让像{3}这样控制匹配重复次数的元字符作用于它前面的这个整体(紧挨着它)。简单来说,子表达式使得正则表达式的一部分成为了一个整体,从而可以让元字符直接作用于这个整体,当然了,这只是子表达式的用途之一,在下一部分,我们还将看到子表达式的另一个重要用途。

这儿还有一个例子,可以说明一些东西。正则表达式19|20\d{2}将被解释为匹配数字字符19或以20开头的任意4位数字;而(19|20)\d{2}将被解释为匹配以19或20开头的4位数字。这个模式很好地表现了子表达式的价值所在,模式里的“|”字符是正则表达式语言里的或操作符。

子表达式允许嵌套,事实上,子表达式允许多重嵌套,这种嵌套在理论上没有限制,但在实际中工作中还是应该遵循适可而止的原则。为了演示嵌套子表达式的用法,我们再去看看刚才那个匹配 IP 地址的例子。下面是我们刚才使用的模式

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

仔细分析,可以发现这个模式还会匹配不合法的 IP 地址。IP 地址由4个字节构成,IP 地址中的4组数字分别对应着那4个字节,所以 IP 地址里的每组数字的取值范围也就是单个字节的表示范围,即0~255。这意味着 IP 地址里的每一组数字都不能大于255,可是上面那个模式还能匹配诸如345、700、999之类的数字序列,而这些数字在 IP 地址里都是非法的。下面是一个合法的 IP 地址的规则:

任何一个1位或2位数字
任何一个以1开头的3位数字
任何一个以2开头、第2位数字在0~4之间的3位数字
任何一个以25开头、第3位数字在0~5之间的3位数字

下面是之前例子的继续:

//测试文本
Pinging hog.forta.com {12.159.46.200} with 32 bytes of data

//正则表达式
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{3})|(2[0-4]\d)|(25[0-5]))

//结果
12.159.46.200

上例中的模式准确无误地做到了只匹配合法的 IP 地址、不匹配非法的 IP 地址。在分析子表达式的时候,应该按照先内后外的原则来进行而不是从第一个字符开始一个字符一个字符地去尝试。

注意:把必须匹配的情况考虑周全并写出一个匹配结果符合预期的正则表达式很容易,但把不需要匹配的情况也考虑周全并确保它们都将被排除在匹配结果以外往往要困难的多。


回溯引用:前后匹配一致

回溯引用指的是模式的后半部分引用在前半部分中定义的子表达式匹配的结果,,看下面例子便懂:

//测试文本
This is a block of of text

//正则表达式
[ ]+(\w+)[ ]+\1

//结果
of of

现在,我们来分析上面这个模式。[ ]+将匹配一个或多个空格,\w+将匹配一个或多个字母数字字符,[ ]+匹配随后的空格。注意,\w+是被括在括号里的,它是一个子表达式。这个子表达式不是用来进行重复匹配的,而是把整个模式中的一部分单独划分出来以便在后面引用。这个模式的最后一部分是 \1,这是一个回溯引用,而它引用的正是前面划分出来的那个子表达式,当(\w+)匹配到单词 of 的时候,\1也匹配单词 of。

\1到底代表着什么?它代表着模式里的第1个子表达式,\2代表着第2个子表达式,\3代表着第3个;以此类推。于是,在上面那个例子里,[ ]+(\w+)[ ]+\1将匹配同一个单词的连续两次重复出现。

使用回溯引用还可以帮助我们完成复杂的替换,下面这个例子体现出了正则表达式的威力:

//测试文本
Hello, ben@forta.com is my email address.

//正则表达式
(\w+[\w\.]*@[\w\.]+\.\w+)

//替换
<a href="mailto:$1">$1</a>

//结果
Hello, <a href="mailto:ben@forta.com">ben@forta.com</a> is my email address.

替换操作需要用两个正则表达式:一个用来给出搜索模式,另一个用来给出匹配文本的替换模式.回溯引用可以跨模式使用,在第一个模式里被匹配的子表达式可以用在第二个模式里。这里使用的模式(\w+[\w\.]*@[\w\.]+\.\w+)与我们以前使用的完全一样(匹配电子邮件),但这次把她写成了一个子表达式。这样一来,被匹配到的文本就可以用在替换操作里了。<a href="mailto:$1">$1</a>使用了两次被匹配的子表达式:一次是在 href 属性里,另一次作为可点击文本。具体到这个例子,ben@forta.com 变成了<a href="mailto:ben@forta.com">ben@forta.com</a>

警告:回溯引用在不同的正则表达式实现里有很大的差异,JavaScript需要用 $ 来代替 \ 进行回溯引用的相关操作。

有些正则表达式实现允许我们使用下表的元字符对字母进行大小写转换

元字符说明
\E结束 \L 或 \U 转换
\l把下一个字符转换为小写
\L把 \L 到 \E 之间的字符全部转换为小写
\u把下一个字符转换为大写
\U把 \U 到 \E 之间的字符全部转换为大写

下面是一个简单的实例:

//测试文本
<h1>Welcome to my Homepage</h1>

//正则表达式
(<[Hh]1>)(.*?)(</[Hh]1>)

//替换
$1\U$2\E$3

//结果
<h1>WELCOME TO MY HOMEPAGE</h1>

模式(<[Hh]1>)(.*?)(</[Hh]1>)把一级标题分成了3个子表达式:开始标签、标题文字、结束标签。第二个模式再把文本重新组合起来: 1\U 2\E`把第2个子表达式转换为大写,$3包含着结束标签。


前后查找

向前查找指定了一个必须匹配但不在结果中返回的模式。从格式上看,向前查找实际就是一个子表达式;而从语法上看,一个向前查找模式其实就是一个以 ?= 开头的子表达式,需要匹配的文本跟在 = 的后面。下面是一个简单的例子:

//测试文本
http://www.forta.com/
https://mail.forta.com/
ftp://ftip.forta.com/

//正则表达式
.+(?=:)

//结果
http
https
ftp

上面的例子中,子表达式 (?=:) 匹配 :,但注意被匹配的:并没有出现在最终的匹配结果中。我们用?=向正则表达式引擎表明:只要找到: 就行了,不要把它包括在最终的匹配结果里。以 ?= 开头的子表达式是向前查找的标志,在使用向前查找的时候,正则表达式分析器将以 ?= 前面的模式为依据以 ?= 后面所跟的字符为基准向前查找,最终匹配的结果不包括 ?= 后面的字符。

任何一个子表达式都可以转换为一个向前查找表达式,只要给它加上一个 ?= 前缀即可。在同一个搜索模式里可以使用多个向前查找表达式,它们可以出现在模式里的任意位置(而不仅仅是出现在整个模式里的开头)。

常见的表达式都支持向前查找,但支持向后查找的就没那么多了。Java、.NET、PHP和Perl都支持向后查找(但有一些限制),JavaScript和ColdFunsion不支持向后查找。

向后查找的操作符是 ?<=,与向前查找操作符的用法大同小异,它必须用在一个子表达式里,而且后要跟匹配的文本。下面是一个例子:

//测试文本
ABC01: $23.45
HGG42: $5.31

//正则表达式
(?<=\$)[0-9.]+

上例中,我们使用了向后查找,从而使得 之后的字符。

警告:向前查找模式的长度是可变的,它们可以包含 .+ 之类的元字符,所以非常灵活。而向后查找模式只能是固定长度,这是一条几乎所有的正则表达式实现都遵守的规则。

下面是一个把向前查找和向后查找结合起来使用的例子:

//测试文本
<title>Ben Forta's Homepage</title>

//正则表达式
(?<=\<title>).*(?=</title>)

//结果
Ben Forta's Homepages

上面这个例子,我们把向前查找和向后查找结合起来使用,很方便地就匹配出了位于 <title></title> 之间的文本。

向前查找和向后查找通常用来匹配文本,其目的是为了确定将被返回为匹配结果的文本的位置(通过指定匹配结果的前后必须是哪些文本)。这种用法被称为正向前查找和正向后查找。术语“正”指的是寻找匹配的事实。另外,前后查找还有一种不太常用的用法叫做负前后查找。负前后查找将向后查找不与给定模式相匹配的文本,听起来与取非匹配有点儿相似。但是却不鞥和使用 ^ 来对前后查找进行取非处理,前面已经说过了,只有当 ^[]连用时,才是有取非意义的。

操作符说明
(?=)正向前查找
(?!)负向前查找
(?<=)正向后查找
(?<!)负向后查找

上表是各种前后查找操作符的说明。一般来说,凡是支持向前查找的正则表达式实现都同时支持正向前查找和负向前查找;类似地,凡是支持向后查找的正则表达式实现都同时支持正向后查找和负向后查找。


嵌入条件

正则表达式语言还有一种威力强大(但不经常被用到)的功能——在表达式的内部嵌入条件处理功能,但并非所有的正则表达式实现都支持条件处理。正则表达式的条件要用 ? 来定义。

回溯引用条件只在一个前面的子表达式搜索取得成功的情况下才允许使用一个表达式。用来定义这种条件的语法是

(?(一个回溯引用)模式1|模式2)
//若此回溯引用存在,即匹配到了期望的文本,则使用模式1进行后续的匹配
//若此回溯引用不存在,即未匹配到期望文本,则使用模式2进行后续的匹配

这里还有一种用法,如果|模式2 没有出现的话,那么之后什么也不匹配,只保留之前的匹配作为最终结果。

下面是一个示例:

//测试文本
123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
123 456 7890
1234567890

//正则表达式
(\()?d{3}(?(1)\)|-)d{3}-\d{4}

//结果
123-456-7890
(123)456-7890

注意:?(1)检查第一个回溯引用是否存在。在条件里,回溯引用编号(本例中的1)不需要被转义。因此,?(1) 是正确的,?(\1)不正确(但后者通常也能工作)。

(\()? 匹配一个可选的左括号,但我们把它用括号括起来得到了一个子表达式。随后的 \d{3} 匹配一位数字的区号。(?(1)\)|-) 是一个回溯引用条件,它将根据条件是否得到满足而去匹配 )- :如果 (1) 存在,也就是找到了一个左括号,\) 必须被匹配;否则,- 必须被匹配。这样一来,只有配对出现的括号才会被匹配;如果没有使用括号或括号匹配不配对,电话号码中的区号和其余数字之间的 - 分隔符必须被匹配。

前后查找条件只在一个向前查找或向后查找操作取得成功的情况下才允许一个表达式被使用。定义一个前后查找条件的语法与定义一个回溯引用条件的语法大同小异,只需把回溯引用(括号里的回溯引用编号)替换为一个完整的前后查找表达式就行了。下面是一个示例:

//测试文本
11111
22222
33333-
44444-44444

//正则表达式
\d{5}(?(?=-)-\d{4})

//结果
11111
22222
44444-44444

\d{5}匹配前5位数字。接下来是一个(?(?=-)-\d{4}) 形式的向前查找条件。这个条件使用了?=- 来匹配一个连字符,如果条件得到满足(那个连字符存在)-d{4} 将匹配那个连字符和随后的4位数字。这样一来,33333- 将被排除在最终的匹配结果之外(它有一个连字符,所以满足给定条件,但那个连字符的后面没有必要出现在那里的4位数字)。

至此,本片读书笔记算是完成了。接下来,再着手写一篇 JavaScript正则表达式 的博客。


附录:常见元字符说明表

基本元字符

元字符说明
.匹配任意单个字符
[ ]匹配字符集合中的一个字符
{^ ]对字符集合求非
-定义一个字符集区间
\对下一个字符进行转义

|    逻辑或操作符


数量元字符

元字符说明
*匹配前一个字符(子表达式)的零次或多次重复
*?*的懒惰型版本
+匹配前一个字符(子表达式)的一次或多次重复
+?+的懒惰型版本
?匹配前一个字符(子表达式)的零次或一次重复
{n}匹配前一个字符(子表达式)的n次重复
{m.n}匹配前一个字符(子表达式)至少m次且至多匹配n次
{n,}匹配前一个字符(子表达式)n次或更多次重复
{n,}?{n,}的懒惰型版本

位置元字符

元字符说明
^匹配字符串的开头
\A匹配字符串的开头
$匹配字符串的结束
\Z匹配字符串的结束
\<匹配字符串的开头
>匹配字符串的结束
\b匹配单词边界(开头和结束)
\B\b的反义

特殊元字符

元字符说明
[\b]退格字符
\f换页符
\n换行符
\r回车符
\t制表符(Tab字符)
\v垂直制表符
\c匹配一个控制字符
\d匹配任意数字字符
\D\d的反义
\w匹配字母数字下划线字符
\W\w的反义
\s匹配一个空白字符
\S\s的反义
\x匹配一个十六进制数字
\0匹配一个八进制数字

回溯引用和前后查找

元字符说明
()定义一个子表达式
\1匹配第一个子表达式;\2代表第二个,依次类推
?=向前查找
?<=向后查找
?!负向前查找
?<!负向后查找
?()条件(if then)

?()|     条件(if then else)


大小写转换

元字符说明
\E结束\E或\U转换
\l把下一个字符转换为小写
\L把后面的字符转换为小写,直到遇见\E为止
\u把下一个字符转换为大写
\U把后面的字符转换为大写,直到遇见\E为止

匹配模式

元字符说明
(?=)分行匹配模式
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值