正则表达式(Regular Expression)学习笔记

今天将完成正则表达式简单应用的系统学习,以弥补以前残缺的认知和了解。

(第一稿位于笔者新浪博客http://blog.sina.com.cn/s/blog_4d1bbec70100qmxg.html


参考:
1. Python正则表达式操作指南
2. 正则表达式30分钟入门教程
3. 深入浅出之正则表达式
简单模式
元字符:
.  ^  $  *  +  ?  {  [  ]  /  |  (  )
字符匹配:
[ ] 构成匹配字符集。-可用于指定区间,元字符在[]中将失去其特殊意义;^和[一起使用时,即[^,表示补集。
反斜杠/ 其后加字符可表特殊含义;其放在元字符前可取消元字符的特殊含义。
/d  匹配任何十进制数;它相当于类 [0-9]。
/D  匹配任何非数字字符;它相当于类 [^0-9]。
/s  匹配任何空白字符;它相当于类  [ /t/n/r/f/v]。
/S  匹配任何非空白字符;它相当于类 [^ /t/n/r/f/v]。
/w  匹配任何字母数字字符;它相当于类 [a-zA-Z0-9_]。
/W  匹配任何非字母数字字符;它相当于类 [^a-zA-Z0-9_]。
/b 代表单词的开头或结尾,也就是单词的分界处。
元字符. 匹配除换行符外的任何字符,即"."通常用于想匹配“任何字符”的地方
元字符^用来匹配用开查找的字符串的开头,而$则匹配结尾。
重复:
元字符* 并不匹配字符“*”,它指定前一个字符可以被匹配零次或多次。
元字符+ 基本和*相同,唯一区别在于+要求至少出现一次。
元字符? 要求匹配一次或零次。
{m,n} 要求至少有m个重复,至多到n个重复
采用python来进行字符的基本方法如下:
yyang@Yanux:~$ python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> p = re.compile("[a-z]+")
>>> print p
<_sre.SRE_Pattern object at 0xb77681e0>
>>> m = p.match("Hello")
>>> print m
None
>>> m = p.match("hello")
>>> print m
<_sre.SRE_Match object at 0xb776ce20>
>>> print m.group()
hello
>>> print m.span()
(0, 5)
对于python,Pattern Object有如下方法:
方法/属性作用
match()决定 RE 是否在字符串刚开始的位置匹配
search()扫描字符串,找到这个 RE 匹配的位置
findall()找到 RE 匹配的所有子串,并把它们作为一个列表返回
finditer()找到 RE 匹配的所有子串,并把它们作为一个迭代器返回

而对于Match object,有如下方法:
方法/属性作用
group()返回被 RE 匹配的字符串
start()返回匹配开始的位置
end()返回匹配结束的位置
span()返回一个元组包含匹配 (开始,结束) 的位置

其中,finditer()如此使用:
>>> iterator = p.finditer('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
>>> for match in iterator:
...     print match.span()              #注意缩进,否则无法运行
... 
(3, 11)
(12, 20)
(25, 31)
(32, 38)
(43, 48)
(49, 50)
(51, 58)
至此,参考文献1还将继续讲解python中更为复杂的正则表达式运用,但此处我需要学习的,则是正则表达式的系统知识。所以暂先离开参考文献1,进入其他资料中进行更加细节的学习。
分支条件:
元字符 |  用于分支条件,即有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用 | 把不同的规则分隔开。
如中国电话区号表示:
0/d{2}-/d{8}|0/d{3}-/d{7},可以表示三位区号或者3位区号的电话,例如021-65987845或0873-2156987
匹配分支时,将会从左到右地测试每个条件,如果满足某个分支,就不会去再管其他条件了。
向后引用:
向后引用用于重复搜索前面某个分组匹配的文本,如/1代表分组1匹配的文本。
/b(/w+)/b/s+/1/b中,/1就是用来匹配前面已经匹配的单词,在实际运用中,purr purr将会被匹配(来自Sheldon那soft kitty)。
强制指定组名,(?<Word>/w+)(将其中的尖括号换成单引号亦可:(?‘Word‘/w+)),这样/w+的组名就成为Word了,向后引用时,采用/k<Word>即可。
上面的例子可以改写为/b(?<Word>/w+)/b/s+/k<Word>/b
参考2中有表如下:
分类代码/语法说明
捕获(exp)匹配exp,并捕获文本到自动命名的组里
(?<name>exp)匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp)匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言:
用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像/b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),它们也称为 零宽断言。(出自参考2的原话,反正我没读懂。。。)
零宽度正预测先行断言 (?=exp)  断言自身出现的位置的后面能匹配表达式exp(这句话中“断言”是名词)。
/b/w+(?=ing/b) 这个正则表达式匹配以ing结尾的单位的前面部分,比如studying while playing中, study和play将会被匹配
零宽度正回顾后发断言(?<=exp) 断言自身出现的位置的前面能匹配的表达式exp,和上面相反。如(?<=/bun)/w+/b,在unable to do中,匹配able
(?<=/s)/d+(?=/s),这个例子匹配以空白符间隔的数字,其中使用了两种断言
(?<=<(/w+)>).*(?=<///1>) 能否看懂此例?
负向零宽断言:
这类断言只匹配一个位置,并不消耗任何字符。
零宽度负预测先行断言 (?!exp) 断言此位置后不能匹配表达式exp. /d{3}(?!/d) 匹配了三位数字并且其后不能是数字。
零宽度负回顾后发断言 (?<!exp) 断言此位置的前面不能匹配表达式exp.  (?<![a-z])/d{7}匹配前面不是小写字母的七位数字
注释:
(?#comment)

尽多匹配与尽少匹配
尽多重复  a.*b    这种情况下,aabab会被全部匹配
尽少重复  a.*b?  这种情况下,aabab会匹配成aab和ab
代码/语法说明
*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n次以上,但尽可能少重复
此处我还不大理解,今后在应用中逐渐弄清楚。

此外,参考2中的“平衡组/递归匹配”我也没理解。下面是更多语法,来自参考2
代码/语法说明
/a报警字符(打印它的效果是电脑嘀一声)
/b通常是单词分界位置,但如果在字符类里使用代表退格
/t制表符,Tab
/r回车
/v竖向制表符
/f换页符
/n换行符
/eEscape
/0nnASCII代码中八进制代码为nn的字符
/xnnASCII代码中十六进制代码为nn的字符
/unnnnUnicode代码中十六进制代码为nnnn的字符
/cNASCII控制字符。比如/cC代表Ctrl+C
/A字符串开头(类似^,但不受处理多行选项的影响)
/Z字符串结尾或行尾(不受处理多行选项的影响)
/z字符串结尾(类似$,但不受处理多行选项的影响)
/G当前搜索的开头
/p{name}Unicode中命名为name的字符类,例如/p{IsGreek}
(?>exp)贪婪子表达式
(?<x>-<y>exp)平衡组
(?im-nsx:exp)在子表达式exp中改变处理选项
(?im-nsx)为表达式后面的部分改变处理选项
(?(exp)yes|no)把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no
(?(exp)yes)同上,只是使用空表达式作为no
(?(name)yes|no)如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no
(?(name)yes)同上,只是使用空表达式作为no

另外,参考3中有对贪婪和懒惰性的讨论,置于此,帮助理解思考。
注意贪婪性
假设你想用一个正则表达式匹配一个HTML标签。你知道输入将会是一个有效的HTML文件,因此正则表达式不需要排除那些无效的标签。所以如果是在两个尖括号之间的内容,就应该是一个HTML标签。
许多正则表达式的新手会首先想到用正则表达式<< <.+> >>,他们会很惊讶的发现,对于测试字符串,“This is a <EM>first</EM> test”,你可能期望会返回<EM>,然后继续进行匹配的时候,返回</EM>。
但事实是不会。正则表达式将会匹配“<EM>first</EM>”。很显然这不是我们想要的结果。原因在于“+”是贪婪的。也就是说,“+”会导致正则表达式引擎试图尽可能的重复前导字符。只有当这种重复会引起整个正则表达式匹配失败的情况下,引擎会进行回溯。也就是说,它会放弃最后一次的“重复”,然后处理正则表达式余下的部分。
和“+”类似,“?*”的重复也是贪婪的。
 
深入正则表达式引擎内部
让我们来看看正则引擎如何匹配前面的例子。第一个记号是“<”,这是一个文字符号。第二个符号是“.”,匹配了字符“E”,然后“+”一直可以匹配其余的字符,直到一行的结束。然后到了换行符,匹配失败(“.”不匹配换行符)。于是引擎开始对下一个正则表达式符号进行匹配。也即试图匹配“>”。到目前为止,“<.+”已经匹配了“<EM>first</EM> test”。引擎会试图将“>”与换行符进行匹配,结果失败了。于是引擎进行回溯。结果是现在“<.+”匹配“<EM>first</EM> tes”。于是引擎将“>”与“t”进行匹配。显然还是会失败。这个过程继续,直到“<.+”匹配“<EM>first</EM”,“>”与“>”匹配。于是引擎找到了一个匹配“<EM>first</EM>”。记住,正则导向的引擎是“急切的”,所以它会急着报告它找到的第一个匹配。而不是继续回溯,即使可能会有更好的匹配,例如“<EM>”。所以我们可以看到,由于“+”的贪婪性,使得正则表达式引擎返回了一个最左边的最长的匹配。
 
用懒惰性取代贪婪性
一个用于修正以上问题的可能方案是用“+”的惰性代替贪婪性。你可以在“+”后面紧跟一个问号“?”来达到这一点。“*”,“{}”和“?”表示的重复也可以用这个方案。因此在上面的例子中我们可以使用“<.+?>”。让我们再来看看正则表达式引擎的处理过程。
再一次,正则表达式记号“<”会匹配字符串的第一个“<”。下一个正则记号是“.”。这次是一个懒惰的“+”来重复上一个字符。这告诉正则引擎,尽可能少的重复上一个字符。因此引擎匹配“.”和字符“E”,然后用“>”匹配“M”,结果失败了。引擎会进行回溯,和上一个例子不同,因为是惰性重复,所以引擎是扩展惰性重复而不是减少,于是“<.+”现在被扩展为“<EM”。引擎继续匹配下一个记号“>”。这次得到了一个成功匹配。引擎于是报告“<EM>”是一个成功的匹配。整个过程大致如此。
 
惰性扩展的一个替代方案
我们还有一个更好的替代方案。可以用一个贪婪重复与一个取反字符集:“<[^>]+>”。之所以说这是一个更好的方案在于使用惰性重复时,引擎会在找到一个成功匹配前对每一个字符进行回溯。而使用取反字符集则不需要进行回溯。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值