正则表达式必知必会(二)

重复匹配

匹配多个字符

匹配一个或多个字符

要想匹配同一个字符(或字符集合)的多次重复,只要简单地给这个字符(或字符集合)加上一个+字符作为后缀就行了。
+匹配一个或多个字符(至少一个,不匹配零个字符的情况)。

比如,a+将匹配一个或多个连续出现的a。[0-9]+匹配一个或多个连续的数字。

在给一个字符集合加上+后缀的时候,必须把+放在这个字符集合的外面。
比如说,[0-9]+是正确的,[0-9+]则不是。

\w+@\w+\.\w+

上面这个例子可以匹配电子邮件格式。

+是一个元字符,如果要匹配+本身,就必须使用转义序列+

但其实xx.xx@xx.xx或者xx@xx.xx.xx也是正确的电子邮件格式,因此修改一下上面的例子:

[\w.]+@[\w.]+\.\w+

这样,[\w.]+将会匹配字母、数字、下划线和.的一次或多次重复出现。

[\w.]中我们并没有将.转义,但仍能匹配.字符本身。
一般来说,当在字符集合里使用的时候,像.和+这样的元字符将被解释成普通字符,不要需要被转义。
但转义了也没坏处,[\w\.]+[\w.]+的作用是一样的。

匹配零个或多个字符

+匹配一个或多个字符,但不匹配零个字符,+最少要匹配一个字符。
那么如果想匹配一个可有可无的字符,需要使用*元字符来完成。

*的用法和+完全一样,只要把它放到一个字符(或字符集合)后面,就可以匹配该字符(或字符集合)连续出现零次或多次的情况。

还是之前匹配电子邮件的例子,我们知道电子邮件一般不应该以.开头,因此这么看来上面的正则表达式还是有点欠缺,我们改成下面这个形式:

\w[\w.]*@[\w.]\.\w+

这样我们限定了第一个字符一定不是.,而是字母数字下滑线中的一个,然后[\w.]*用于匹配剩下的@之前的字符,可以没有,也可以出现多次。

匹配零个或一个字符

另一个非常有用的元字符是?。?只能匹配一个字符(或字符集合)的零次或一次出现,最多不超过一次。

https?://[\w./]+

上面的表达式重要的是https?的部分,意思是可以匹配http或者https。
后面的[\w./]+用于匹配一个URL地址的主体部分,字母、数字、下划线、.
和/任意出现。

再看一个例子,在windows系统中匹配空白行需要\r\n\r\n,而在Unix或Linux系统中则只需要\n\n。因此我们可以写出下面的式子:

[\r]?\n[\r]?\n

我刚看到这的时候也有疑问,直接写成\r?\n\r?\n不就行了。
[\r]定义了一个字符集合,该集合只有元字符\r这一个成员,因此其实[\r]和\r在功能上完全一样。用[ ]将单个字符包围起来定义成一个集合的做法可以增加可读性,避免产生误解。

匹配的重复次数

正则表达式里的+、*和?解决了许多问题,但又远远不够:

  • +和*匹配的字符个数没有上限,无法为匹配的字符个数设定一个最大值。
  • +、*和?至少匹配零个或一个字符,我们无法为它们的匹配个数设定一个最小值。
  • 如果只是用+和*,我们无法将它们的匹配个数设定为一个精确的数字。

为了解决这些问题,并让程序员对重复性匹配有更多的控制,正则表达式提供了一个用来设定重复次数的语法。
重复字数用{和}来给出,把数值写在它们之间。

为重复匹配次数设定一个精确的值

{3}意味着模式里的前一个字符(或字符集合)必须在原始文本里连续重复出现3此才算是一个匹配。

在上一篇中举过用正则表达式匹配RGB颜色值的例子,当时是这么写的:

#[A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9]

那么现在就可以改进为:

#[A-Fa-f0-9]{6}

为重复匹配次数设定一个区间

{}还可以用来为重复匹配次数设定一个区间,也就是为重复匹配次数设定一个最小值和一个最大值。
这种区间必须以{2,4}这种形式给出。

下面的这个例子用于匹配日期:

\d{1,2}[-/]\d{1,2}[-/]\d{2,4}

这个规则可以匹配 01-02-1992,3-10-06,16/9/2010 这些格式的日期。

重复次数可以是0。比如{0,3}表示重复次数可以是0,1,2或3。
?用于匹配0个或1个字符,其实就等价于{0,1}

匹配至少重复多少次

{}还可以给出最小的重复次数,但不必给出一个最大值。
{}的这种做法和我们用来为重复匹配次数设定一个区间语法很相似,只是省略了最大值部分。
比如,{3, }表示至少重复3次。

举个例子,找出大于$100的面值:

$\d{3,}

这个式子至少匹配3位数字,因此就能找出大于$100的美元数值。

在这种重复次数匹配时一定要注意不要遗漏,不然就会变成精确匹配了。

防止过度匹配

?只能匹配零个或一个字符,{n}和{n,m}也有一个重复次数的上限。也就是说,这几种语法所定义的“重复次数”都是有限的。
但是像+和*就是无限匹配的语法,很有可能造成过度匹配的现象。

比如说我们想匹配HTML文档中的<b>标签及其之中的内容,我们可能会写出如下式子:

<[Bb]>.*</[Bb]>

<[Bb]>用于匹配大写或小写的b标签,</[Bb]>则用于匹配</b> 或其大写形式。.*用于匹配任意多个字符。

但是对于<b>ssss</b>and<b>hi</b>,这个正则表达式只会匹配出一个结果,原因就是第一个<b>和最后一个</b>之间的所有东西都被.*覆盖了。

*和+都是所谓的贪婪型元字符,它们在进行匹配时会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。

为了解决这种问题,一般使用这些贪婪型字符的懒惰型版本(懒惰在这里的含义是匹配尽可能少的字符,与贪婪型元字符的行为模式刚好相反)。
懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个?后缀即可。

  • *?
  • +?
  • {n, }?

再回到刚才的例子,<[Bb]>.*?</[Bb]>改进了刚才的例子,使用了懒惰型版本,因此可以匹配两个结果:
<b>ssss</b>
<b>hi</b>

位置匹配

位置匹配即只对某段文本的特定位置进行匹配。

单词边界

第一种边界是由限定符\b指定的单词边界。\b用来匹配一个单词的开始或结尾。

比如,我们指定如下正则表达式:

\bcat\b

这将只会匹配cat,而不会匹配scats这种字符串。

到底什么是单词边界?单词边界的位置位于一个能够用来构成单词的字符和不能用来构成单词的字符中间。其实也就是\w与\W之间。

这里要特别注意的是,如果想匹配一个完整的单词,就必须在想要匹配的文本前后都加上\b限定符。
如果将式子改为\bcat,则除了cat之外,也会匹配cats,catch等单词。也就是变成匹配以cat开头的单词。

同理,cat\b则会匹配以字符序列cap结束的单词。

\b匹配且只匹配一个位置,不匹配任何字符。
用\bcat\b匹配到的字符串的长度是3个字符,而不是5个字符。

如果想表明不匹配一个单词边界,则使用\B。

不是一个单词边界,那么就是\w与\w之间,或者\W与\W之间的位置。

例如,\B-\B用于匹配一个前后都不是单词边界的连字符。 -能够被匹配,但是a-b则不能被匹配。

正如我们之前看到的那样,同一个元字符的大写形式与其小写形式在功能上往往刚好相反。

字符串边界

单词边界可以用来进行与单词相关的位置匹配(单词的开头、单词的结束、整个单词等)。
字符串边界有着类似的用途,只不过用来进行与字符串有关的位置匹配而已。(字符串的开头、字符串的结尾、整个字符串等)。

用来定义字符串边界的元字符有两个:

  • 定义字符串开头的^
  • 用来定义字符串结尾的$

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

举个例子,我们想要检查一个文档是否是XML文档,就要检查文档开头有没有以<?xml>开头的字符串,比如<?xml version="1.0"?>

<\?xml.*\?>

这个模式似乎能够解决问题,但是这个会匹配整个文本中任何位置出现的<?xml>字符串,而不是出现在文档的开头。
因此我们需要的是一个能够确保被匹配到的<?xml>标签出现在字符串最开始处的测试:

^\s*<\?xml.*\?>

^匹配一个字符串的开头位置,所以^\s*将匹配一个字符串的开头位置和随后的零个或多个空白字符。

除了位置上的差异,$与^的用法完全一样。
比如说在一个web页面里,</html>后面不应该再有任何实际内容,这一点可以用下面这个模式来检查:

</html>\s*$

\s*$匹配一个字符串结尾处的零个或多个空白字符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值