先引用网上看到的一段话:
正则表达式具有伟大技术发明的一切特点,它简单、优美、功能强大、妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地提高开发效率和程序质量。
我觉得他说的不对,我们稍微改下:
正则表达式具有伟大技术发明的一切特点,它简单、
优美不优美、功能强大、妙用无穷。对于很多实际工作来讲,正则表达式简直是灵丹妙药,能够成百倍地提高开发效率和程序质量。
为什么说他不优美,因为正则表达式可读性极差,在我看来绝对不属于优美的范畴。但是他的强大确是无法反驳的。
基础
元字符
反义
使用[^]
声明翻译,或是元字符中改为大写,如\B \D \S
。
量词
先解释关于量词所涉及到的重要的三个概念:
贪婪(贪心) 如”*”字符 贪婪量词会首先匹配整个字符串,尝试匹配时,它会选定尽可能多的内容,如果 失败则回退一个字符,然后再次尝试回退的过程就叫做回溯,它会每次回退一个字符,直到找到匹配的内容或者没有字符可以回退。相比下面两种贪婪量词对资源的消耗是最大的。
懒惰(勉强) 如 “?” 懒惰量词使用另一种方式匹配,它从目标的起始位置开始尝试匹配,每次检查一个字符,并寻找它要匹配的内容,如此循环直到字符结尾处。
占有 如”+” 占有量词会覆盖事个目标字符串,然后尝试寻找匹配内容 ,但它只尝试一次,不会回溯,就好比先抓一把石头,然后从石头中挑出黄金
区间
使用[a-z]表示匹配a-z区间任意字符
简单使用
有了上述基础知识后,我们就可以用上述元字符来写一些简单的正则表达式了,让我们看些例子。
11位手机号
手机号因为以1开头,后续10位数字,所以简单写法如下
^1\d{10}$
邮箱匹配
思路
我们看下邮箱的构成: 邮箱名@n级域名.(m级域名.)(域名后缀)。m级域名可能出现多次,也可能不出现。而邮箱名可以是任意的字母数字下划线和短横线(不考虑中文)。这样我们的匹配规则就很明确了。
patten=^[\w-]+@([\w-]+.)+([a-z]+)$
注:这里只做简单的例子,并不考虑非常复杂、以及特殊的情形。
解释
- 头部的
^
声明了^
后方的字符应出现在字符串头部。$
声明$
前方的字符应出现在字符串末尾。所以模式用于匹配整个字符串。 -
首先这里使用
[\w-]
用来匹配邮箱名可以是字母数字下划线和短横线,后面的+
声明[\w-]
匹配的字符必须出现一次或者多次。 -
@
用于匹配邮箱名中的@。 -
[\w-]+.
用于匹配多级域名中的每一级。由于域名最低为一级域名,所以上述的模式最少出现一次。最终写为([\w-]+.)+
([a-z]+)
用于匹配域名后缀,如com、cn等。
进阶
分组
捕获分组
我们使用()
对模式进行分组,看个例子
(a|d)*c
上面这个例子里,(a|d)*
表示出现a或者d 0次或多次。由于*只匹配前方的单个对象,如果我们需要匹配的条件比较负责,就使用()
进行分组。本质和()
本身含义一致。
命名
上面已经进行了分组,所以可以使用分组编号(下标来使用),但是如果我想指定名称时,就需要命名分组了。
(?<name>exp)
通过<name>
指定命名,如捕获座机号码区号和号码部分:
(?<quhao>\0\d{2})-(?<haoma>\d{8})
用于命名区号和号码的分组捕获。
非捕获分组
使用有时我们设定捕获组只是为了定位目标,但是实际并不需要该组的内容。那么可以利用非捕获组声明不捕获。
用法
(?:exp)
如还是号码问题,如果只需要号码,那么就需要对区号部分声明非捕获组:
(?:\0\d{2})-(\d{8})
反向引用
先举个例子,如果我们需要捕获一组aabbccdddddgseddbb
里成对出现的元素,如aa
,bb
这样。那么就需要反向引用了。
首先思路大致是先匹配一个字符,然后查看下一个是否与他相等即可。刚刚分组里已经将了分组是有编号的。所以我们通过编号反向引用前方分组的内容,就可以用来判断两个是否相等了。
用法
number或\name
如\1
引用第一个分组,\quhao
引用名为quhao
的分组。
再回到刚刚的问题,那么写法就如下了。
(\w)\1
输出如下:
aa
bb
cc
dd
dd
dd
bb
零宽断言
断言
断言表示断定目标中会出现的字符。零宽
表示断言所占的宽度为0,即只匹配断言的位置,并不输出。
正向先行断言
正向是指断言的内容为真时匹配,与此相反的还有后面讲到的反向,反向就是只断言内容为假时匹配。先行是指匹配的方向,即内容在前,断言在后。
用法
...(?=patten)
正向后行断言
与线行相反,匹配内容在后,断言在前。看个例子就明白了。
用法
(?<=patten)...
正向先/后行断言例子
假设我们要用爬虫抓取csdn里的文章阅读量。通过查看源代码可以看到文章阅读量这个内容是这样的结构
<span class="read-count">阅读数:641</span>
所以有两种方式
- 通过前方的
<span class="read-count">阅读数:
部分定位目标。 - 通过后方的
</span>
定位目标
正向先行断言实现
d+(?=</span>)
通过\d+
匹配数字。(?=</span>)
声明正向先行断言。
正向先行断言实现
(?<=<span class="read-count">阅读数:)\d+
声明正向后行断言,正确匹配。
反向先行断言
和正向先行断言相反,反向先行行断言只匹配与断言不相符的先行部分。
用法
....(?!patten)
反向后行断言
类比上面,反向后行只匹配与断言不相符的后行部分。
用法
(?<!patten)...
反向后行断言例子
假设我们要捕获一个博客页面的标题和文章内容。样例如下。
<div>
<p class="title">标题</p>
<p class="content">678</p>
</div>
<div class="foot">页脚</div>
可以看到,标题和内容的格式并不一样,并且还有页脚内容,所以我们需要将页脚内容排除。这里就使用反向后行断言+正向先行断言实现。
(?<!<div class="foot">).+(?=</p>)
发表评论