正则表达式学习笔记
元字符
1,行的起始与结束^,$。^cat最好理解为以c作为一行的第一个字母,紧跟一个a,再接一个t的文本,而不是以cat开头的行,这样更易于明白新遇到的正则表达式的内部逻辑。比如:^$,^,前一个代表一个空行,后一个无任何意义,它匹配一个行起始符,而每一行都有一个行起始符。
2,字符组[]。在字符数组里,基本上的特殊字符都变成了普通字符,除了^用于字符数组最左边,或-用于字符数组内部的时候。另外一点要明白的是排除型字符组也得匹配文本 ,eg:q[^u]它就不能匹配Iraq,它匹配的是q后面跟一个不是u的字符,至少得有一个字符。
3,多选结构(str1|str2)。字符组只能匹配单个字符,它能匹配任意长度的文本。
4,单词分界符\<,\>。它们用来匹配单词的起始与结束位置,与^和$类似。不过它不能真正意义上的区分单词,这时原单词一般指的是只数字,字母,下划线的序列o。eg: \-\'\ $\<1995\>
可选项元素
5,?代表它的前一个匹配规则可以出现,也可以不出现。可以是字符,也可以是多选结构等。
6,+代表它的前一个匹配规则可以连续出现一次或多次,但是至少一次。尽可能多地匹配。
7,*代表它的前一个匹配规则可以连续出现零或多次。尽可能多地匹配。
8,{m,n}代表它的前一个匹配规则重复出现的区间。如果只指定m,{m}则明确重复的次数。
9,括号的另一种用法,在许多流派的正则表达式中(注意不是全部),括号能够“记住”它们包含的子表达式匹配的文本,然后在后面傅\1,\2等来引用,一般来说,不能超过9个,另外也不是全部都用\1这种形式。eg:(["']).*\1。
10,转义\。在特殊字符前加它,特殊字符将会失去特殊性。
表达式的匹配原理
1,正则引擎主要可以分为基本不過的两类:一种是DFA,另一个是NFA。NFA又可以分为传统型NFA和POSIX NFA。 只有传统NFA支持忽略优先题词,DFA不支持捕获型括号和回溯,可以以此来测试引擎属于哪一类。
to(ni(ght|te)|knight) tonite|tonight|tonight
DFA匹配属于文本主导式,每个字符只会匹配一次,在匹配过程中,会记录所以有的可能匹配,然后在匹配过程中逐步淘汰,所以对它来讲上面这两种写法在效果上并无区别。
NFA匹配属于表达式主导,它的最典型特殊是回溯,比如分枝结构,当前字符会对每个结构逐一尝试。这样会导致某些字符重复匹配,所以正则表达式写得好与坏直接影响匹配速度。
2,匹配的两条普适的规则:
I:优先选择最左端的匹配结果。比如字符"how are you",用(you|how)来进行匹配的话,将会是匹配how,而不是you。因为匹配是首先将第一个字符用于尝试,如果能够失败则用下一个字符进行尝试。比如刚才的,首先用h来进行尝试,可以发现h及其后的两个字母能够匹配,返回,假设匹配条件制成(you|hoo),则h不能匹配,再换下一个字母o进行匹配,以此类推。
II:标准量词在匹配优先的。标准量词(?,*,+,{m,n}),它们总是尝试获得最长的匹配。比如有字符串"ftp2007",那么\w+(\d+)中的括号将捕获什么呢?它将捕获7,因为\w会可能多地匹配,也就是说它会匹配整个串,但是后来的\d+要求至少匹配一次,所以它会“不情愿”地释放一个7,当\d+满足要求后,匹配结束。
3,NFA在匹配过程中会留下许多备用状态,匹配失败后的回溯原则是LIFO(last in first out),对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试“。
4,匹配优先的问题。如果对字符串The name "McDonald's" is said "makudonarudo" in Japanese用".*"来匹配的话,结果恐怕不是我们想要的。可以使用"[^"]",不过大多数流派中,[^"]是能匹配换行符的。这种问题也同样出现在对HTML的tag的匹配中。为这时可以使用某些NFA支持的忽略优先量词,忽略优先量词是在量词后面加一个?。不过需要注意的是,用.*?来匹配...Billions and Zillions...时,可能也不是我们所希望的,所以具体问题还得具体分析。
5,环视。如果支持环视的话,可以用环视来解决这一问题。
((?!).)*? 或者((?!?B>).)*
环视的作用是,如果当前位置紧接着的不是指定的值时,进行匹配。上面第二个的含义就是如果当前位置后面紧跟的不是或时进行匹配,从而在和之间,肯定会不再出现它们中的任何一个。这样就可以不用忽略优先了。
环视分为顺序环视与逆序环视,每一种又分为肯定和否定的,(?=...),(?!...),(?<...),(?regex)可以用(?+(regex))\1来模拟,比如(?>\w+):等价于(?=(\w+))\1:
Perl和Python的限制是最严格的,逆序环视只能匹配固定长度的文本,比如可以使用(?
6,固化分组。先来看一个问题,将浮点数规格化,保留两个有效数字,如果第三位是零则舍去,其它则保留。很容易想到的是$data =~ s/(\.\d\d[1-9]?)\d*/$1/;这是对的,不过假设此数字是.625,那么替换过程变成了.625替换成.625,这种替换是没有必要的,可以改进,将\d*换成\d+,这样,.625就没法匹配,就不会替换了。但是现在问题又来了,按照匹配规则,上面得到的结果将是.62,因为最后一位5会被\d+占用。所以此时,如果在5匹配后不会再进行回溯的话,那么这个问题就可以得到完美的解决,这就可以使用固化分组。
固化分组(?>...),它与正常的匹配并无差别,但是如果匹配进行到此结构之后,那么此结构体中的所有备用状态都会被放弃,也就是说,在固化分组匹配 结束后,已经匹配过的文本只能作为整体保留或放弃。可以将上面的改为:$data =~ s/(\.\d\d(?>[1-9]?)\d+/$1/;这样上面的5匹配后,由于前面已经没了了回溯状态,所以后面的\d+不能匹配,从而整个匹配失败,所以不会替换。
固化分组因为放弃了备用状态,所以可能改变匹配结果,导致本来能匹配的匹配失败,也有可能加快报告匹配失败的速度。比如用\w+:来匹配subject nothing,回溯是没有必要的。
(?>.*?)将匹配什么文本呢?答案是什么也不匹配,因为它将不断地以忽略优先的方式进行匹配,然后不断放弃备用状态。如果是(?>.*),只要它后面接了任意必须匹配的字符,就会导致匹配失败,因为它会以匹配优先的方式将所以字符匹配,然后放弃所以备用状态,从而导致后面的匹配匹配失败。
(?>M)+与(?>M+)相对,前者无意义,因为M不会产生任何的回溯状态,而后一个则有意义。
7,占优量词,在标准量词后加+,eg:?+,*+,++,{m,n}+。
它与固化量词的作用差不多。
8,多选结构不是匹配优先,也不是忽略优先,而是按顺序排列,也就是如果如果多选结构中,有多个选项能够匹配,那么选择最左边一个。这有可能出现有序多选结构陷阱。比如:
匹配日期:如果用jan (0?[1-9]|[12][0-9]|3[01])来匹配的话,理想情况是第一组匹配0开始的9天或者只是个位数,第二级1,2开始的,第三级3开始的,但是如果将其用于匹配 jan 31时,只会匹配jan 3,因为这已经满足最左边的情况了。
9,DFA匹配的是最长文本,NFA则不一定,POSIX标准规定,如果在字符串的某个位置存在多个可能的匹配,应当返回的是最长的匹配。速度上,DFA的速度与正则表达式无关,而NFA中两者直接相关。不过NFA能支持DFA不支持一一些功能:捕获由括号的子表达式匹配的文本,环视以及其他复杂的零长度确认,非匹配优先量词,占有优先量词和固化分组。
阅读(1201) | 评论(0) | 转发(0) |