上一篇:《精通正则表达式第三版》读书笔记1
可选项元素?
(量词:?
)
例子1:colou?r
现在来看color
和colour
的匹配。他们的区别在于,后面的单词比前面的单词多一个u
,我们可以用colou?r
这个正则表达式来解决这个问题。元字符?
(问号),代表可选项。把?
加在一个字符的后面,就表示此处允许出现这个字符,也可以不出现这个字符,这个字符的出现并非匹配成功的必要条件。
u?
中的?
元字符?
与我们之前看到的元字符都不相同,它只作用不之前紧邻的元素。因此colou?r
的意思是:先依次匹配字符c
,o
,l
,o
,然后是u?
,最后是r
。
u?
是必然能匹配成功的,有时候它会匹配字符u
,其他时候则不匹配任何字符。关键在于u
是否出现u?
都是匹配成功的。但是这并不意味着,任何包含?
的正则表达式都永远能匹配成功,例如colo
和u?
都能在semicolon
中匹配成功,(colo
匹配semicolo
n中的colo
,u?
则什么都不匹配)。但是之后的r
无法匹配,所以最终正则表达式colou?r
无法匹配semicolon
实例:
questionMark.txt:
color colour semicolon
命令:egrep "colou?r" questionMark.txt
,匹配效果如下。
grep好像不支持可选元字符?,上面的命令如果用grep "colou?r" questionMark.txt
将无法匹配。
例子2
来看另一个例子,我们需要匹配表示7
月4
日的文本(July
fourth)
的文本,其中月份可能写作July
或者是Jul
,而日期可能写作fourth
,4th
,或者4
。显然我们可以使用(July|Jul).(fourth|4th|4)
,不过也可也使用其他方法来结局这个问题。
首先我们把(July|Jul)
缩短为July?
。去除了多选元字符|
之后就没必要保留括号了。所以July?
显然更加简洁。于是得到新的正则表达式:July?.(fourth|4th|4)
现在看看第二部分,我们可以把4th|4
简化为4(th)?
,可以看到?
现在作用的是整个括号了,括号内可以是任意复杂的正则表达式,但是从括号外面来看
它们是一个整体。可以看到这里的括号()
用来限定?
的作用对象。同样的道理使用括号()
还可以用来限定其他类似元字符的作用对象,这是括号()
的主要用途之一。
现在正则表达式变成了July?.(fourth|4(th)?)
。尽管这个正则表达式包含了许多元字符,但是理解起来并不困难。
实例:
dates.txt:
Wen July 4 17:39:57 CST 2018
Wen Jul 4th 17:39:57 CST 2018
Wen Jul fourth 17:39:57 CST 2018
使用正则表达式July?.(fourth|4(th)?)
匹配,命令:egrep "uly?.(fourth|4(th)?)" dates.txt
,执行结果如下。
使用正则表达式(July|Jul).(fourth|4th|4)
进行匹配,命令:egrep "(July|Jul).(fourth|4th|4)" dates.txt
,匹配结果如下。
其他量词:重复出现
加号+
和星号*
的作用于问号?
类似,元字符+
表示之前紧邻的元素可以出现一次或多次,而元字符*
表示之前紧邻的元素可以出现任意多次,甚至不出现。换种说法就是:
- *
表示匹配尽可能多的次数,如果实在无法匹配也不要紧。
- +
的意思是匹配尽可能多的次数,但是至少要匹配一次
,如果连一次也无法匹配,则匹配失败。
量词:问号?
,加号+
,星号`
问号?
,加号+
,星号*
这三个元字符统称为量词,它们限定了作用元素的匹配次数。
三个量词的异同
和问号?
一样量词星号*
也是永远不会匹配失败的,区别值在于它们的匹配结果,而加号+
在无法进行任何一次匹配是,会报告匹配失败。
举例来说:
.?
能有匹配一个可能出现的空格,但是.*
能匹配任意多个个空格。
量词应用实例
实例1:匹配<H[1-6]>
的这样的HTML tag
作者的例子:
我们可以用着两个量词来简化上一篇文章中的<H[1-6]>
的例子,按照HTML规范,在tag结尾的>
字符之前可以出现任意长度的空格,例如<H3___>
或者<H4_________>
等等(这里用下划线代替空格,因为在markdown里写空格好像显示出来)。
把.*
加入正则表达式中可能出现空格的位置,就得到了:H[1-6].*
。这个正则表示是依然能匹配<H1>
,因为空格并不是必须出现的,单其他形式的tag
也能匹配。
我的实例验证:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1 >一级标题</h1>
<h2 >二级标题</h1>
<h3 >三级标题</h1>
<h4 >四级标题</h1>
<h5 >五级标题</h1>
<h6 >六级标题</h1>
</body>
</html>
使用正则表达式[Hh][1-6].*
,进行匹配: egrep "[Hh][1-6].*" CharSet.html
可以看到,什么都没匹配到,就匹配到一些空行,看来作者这里写的有问题,还是不要用点号.
代替空白符的好,而是使用空白符\s
,我这里把正则表达式为[Hh][1-6]\s*
。
执行命令:egrep "[Hh][1-6]\s*" CharSet.html
从匹配的结果中可以看到,空白符也匹配到了,已经用红色标记出来了。当然上面也没有完整匹配到HTML标签,应该加入HTML标签的开始符合结束符<
和>
,修改后的匹配命令为:egrep "<[Hh][1-6]\s*>" CharSet.html
可以看看到上面成功匹配的HTML标签的开始符如<h1>
,但是结束符</h1>
,没能匹配到。观察这了两个标签可以看出,结合标签多了一个反斜杠/
,这个时可以加入问号?
这个量词,这样就能同时匹配HTML的开始符和结束符。修改后的正则表达式为:</?[Hh][1-6]\s*>
,执行命令: egrep "</?[Hh][1-6]\s*>" CharSet.html
,匹配效果如下。
还有一个就是,在Ubuntu终端中,空白符是默认不显示的,可以用鼠标选中这些匹配的结果,就可看到空白符是不是被选中了,如果选中了会有标记红色。
实例2:匹配类似<HR SIZE=14>
这样的HTML tag
接下来看类似<HR SIZE=14>
这样的HTML tag
,它表示一条高度我14像素的穿越屏幕的水平线,与<H3>
的例子一样,在最后的尖括号之前可以出现任意多个空格。此外在等号=
两边也允许出现任意多个空格。最后HR和SIZE之间最少有一个空格。为了简便处理使用点号.
来匹配空空格,然后该空格后面可以有多个空格可以用.*
表示。这样的的正则表达式为<HR..*SIZE.*=.*14.*>
。虽然这样能实现效果,但是还是用.+
来替代..*
比较简洁。所以得到的正则表达式为:<HR.+SIZE.*=.*14.*>
。
验证:
HR.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试匹配水平线</title>
</head>
<body>
<hr size=14>
<hr size=14>
<hr size =14>
<hr size = 14>
<hr size = 14 >
</body>
</html>
使用正则表达式<HR..*SIZE.*=.*14.*>
进行匹配: egrep -i "<HR..*SIZE.*=.*14.*>" HR.html
:
使用正则表达式<HR.+SIZE.*=.*14.*>
进行匹配:egrep -i "<HR.+SIZE.*=.*14.*>" HR.html
可以看到两个正则表达式匹配的效果都是一样的
尽管上面的<HR.+SIZE.*=.*14.*>
这个正则表达式不受到空格个数的限制,单他仍然后tag
中水平直线大小约束,我们要找的不仅仅是高度为14
的tag
,而是所有的tag
,所以我们必须用能有匹配普通数值的表达式来替换上面的14
。在这里,数值是由一位或者多位数值构成的[0-9]
可以表示一个数字,因为至少出现一次,所以使用加号+
量词,结果就是使用[0-9]+带代替<HR.+SIZE.*=.*14.*>
中的14。一个字符组是一个元素
(一个元素最终只匹配一个字符),所以它可以直接加上加号+
,星号*
,和问号?
而不需要加括号限定。
这样我们就得到了正则表示式:<HR.+SIZE.*=.*[0-9]+.*>
现在使用这个新的正则表达式<HR.+SIZE.*=.*[0-9]+.*>
匹配上面的HR.html
文件,命令: egrep -i "<HR.+SIZE.*=.*[0-9]+.*>" HR.html
<HR.+SIZE.*=.*[0-9]+.*>
这个正则表达式看起来比较诡异,这是因为这里使用了星号和加号作用的对象空格,人们习惯吧空格跟普通的字符区分开。在阅读正则表达式时,要改变这种习惯,因为空格也是普通字符之一,它与a
,b
,c
…0
,1
,2
…没有任何差别。
实例3:同时匹配<HR>
,<HR SIZE=14>
这样的HTML tag
我们继续这个例子,如果尺寸这个属性也是可选的,也就是说<HR>
就代表默认高度的直线(同样在>
之前也可能出现空格)。现在修改上面的正则表达式实现这个功能。
可选项:(子表达式)?
可选项的意思是可以出现一次,也可以不出现,例如上面的<HR>
和<HR SIZE=14>
中,SIZE=14
就是可选出现的。这样的可选项可以使用问号量词?
实现,因为可选项SIZE=14
多余一个字符,所以要用括号()
进行限定,可选项的正则表达式为(.+SIZE.*=.*[0-9]+.*)?
,所以上面的问题的最终的正则表达式为:<HR(.+SIZE.*=.*[0-9]+.*)?.*>
。
要注意的是结尾的.*
出现在可选项(...)?
之外,这样就能应付<HR.>
之类的情况。同样要注意的时括号里面的SIZE
前面的.+
也不能少,如果把它拿到括号外,则HR
之后就必须要有一个空格,这样即使SIZE没有出现,也是必须要有一个空格。这样就无法匹配<HR>
这种情况了。
上面的正则表示式中把可选的字表达式(.+SIZE.*=.*[0-9]+.*)?
和.*
交换一下也是可以的:<HR.*(.+SIZE.*=.*[0-9]+.*)?>
代码验证:
修改后的HR.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试匹配水平线</title>
</head>
<body>
<hr size=14>
<hr size=1>
<hr size =2>
<hr size = 3>
<hr size = 4 >
<hr>
<hr >
</body>
</html>
使用上面的正则表达式<HR(.+SIZE.*=.*[0-9]+.*)?.*>
进行匹配, egrep -i "<HR(.+SIZE.*=.*[0-9]+.*)?.*>" HR.html
可以看到不管是<hr>
还是<hr >
,还是<hr size=14>
以及有空格的情况。<HR(.+SIZE.*=.*[0-9]+.*)?.*>
这个正则表达式都能匹配到。
使用另个一个正则表达式<HR.*(.+SIZE.*=.*[0-9]+.*)?>
进行匹配,egrep -i "<HR.*(.+SIZE.*=.*[0-9]+.*)?>" HR.html
要注意的是,每个量词都规定了匹配成功至少需要的次数的下线,以及尝试匹配次数的上限,对于量词?
和量词*
,下限是0
,而量词+
下限是1
。而对于+
和*
这两个量词,上限是无穷大,而。量词?
的上限是1
量词小结
序号 | 量词 | 次数下限 | 次数上限 | 描述 |
---|---|---|---|---|
1 | ? | 0 | 1 | 可以出现一次,也可以不出现(单次可选) |
2 | * | 0 | 无穷大 | 可以不出现,也可以出现任意次(任意次均可) |
3 | + | 1 | 无穷大 | 可以出现无数次,但至少出现一次(至少一次) |