pcre和正则表达式的误点

Posted 骏马金龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pcre和正则表达式的误点相关的知识,希望对你有一定的参考价值。

本文只是关于正则一些容易出错的地方,关于正则的学习,可参考如下两篇文章:

基础正则:https://www.cnblogs.com/f-ck-need-u/p/9621130.html

Perl正则:https://www.cnblogs.com/f-ck-need-u/p/9648439.html

1.正则中所有的匹配模式,都应该理解为"匹配了某字符或字符串后,紧跟着再匹配"。这个概念很重要。

2.中括号首部使用脱字符时,表示的是紧跟着匹配不含给定字符的字符,而不是允许不匹配给定的字符。
它们大多数时候是等价的,但在匹配行尾时,意义不同,例如:Aa[^bcd]$ 所匹配的行允许是Aaa$或Aax$,但不允许仅是Aa$。
这就是正则中"紧跟着匹配"的意思。

3.(\\.[0-9]+)? 可匹配小数点部分,不能写成 (\\.?[0-9]*) ,后者即使不能匹配小数点,也能匹配原本处于小数点后的数值

4.星号*匹配问题0或多个字符,如果写成"a*",将可以匹配任何单个字符,只不过对于非a字符,匹配到的结果为空。

例如字符串"111aaaAAA",正则"1a*"其实从读取第2个字符进行匹配的时候就已经匹配完成了,匹配的结果不是"1aaa",而是"1"加上1前面的一个空,因为是从行首这个锚定位开始匹配的。使用sed或perl进行替换操作就很容易理解。

$ echo \'111aaaAAA\' | perl -lne \'print s/a*/b/rg\'
b1b1b1bbAbAbAb        # 每一个非a字符前都被替换了

$ echo \'111aaaAAA\' | perl -lne "/1a*/;print $&;print $\'"
1                   # 表示匹配到的内容
11aaaAAA       # 表示匹配后剩下的内容

这可能不是很好理解。但其实想想".*"的作用,其实他们是同一类的写法,正如".*"并不是先用点去匹配一个字符后再通过星号去重复这个字符,而是直接表示匹配可能任意多个的任一字符。不同之处在于,".*"匹配任何单个字符,而"a*"匹配任何单个字符,包括空位置。

另外,有些语言在处理正则的方式上有些差异,特别是在测试"a*"的时候,grep/sed/perl均有所不同,不过python/perl/ruby之类的语言在处理这个问题上,结果都一样。

5.perl正则括号分组时,使用(?:替代左括号(,可以表示只分组不捕获。所谓的捕获表示的是可以反向引用或保存到正则外部的变量中
([-+]?[0-9]+(\\.[0-9]+)?) *(cm|mm) :(cm|mm)将保存为$3
([-+]?[0-9]+(?:\\.[0-9]+)?) *(cm|mm) : (cm|mm)将保存为$2

6.特殊锚定符,锚定所匹配的是位置,而非字符,行首^和行尾$同样如此。
注意某些程序对单词的理解和边界定义不一样。且有些程序并不完全支持下列所有的特殊元字符。一般来说,单词是由字母、数字和下划线组成的,即[a-zA-Z0-9_]。
例如gnu grep 2.6版本就不支持\\s和\\d,而gnu grep 2.20支持\\s但不支持\\d
\'\\b\':匹配单词边界处的空字符Match the empty string at the edge of a word.
\'\\B\':匹配非单词边界处的空字符Match the empty string provided it\'s not at the edge of a word.
\'\\<\':匹配单词开头处的空字符Match the empty string at the beginning of word.
\'\\>\':匹配单词结尾处的空字符Match the empty string at the end of word.
\'\\w\':匹配单词构成部分Match word constituent, it is a synonym for `[_[:alnum:]]\'.
\'\\W\':匹配非单词构成部分Match non-word constituent, it is a synonym for `[^_[:alnum:]]\'.
\'\\s\':匹配空白字符Match whitespace, it is a synonym for `[[:space:]]\'.
\'\\S\':匹配非空白字符Match non-whitespace, it is a synonym for `[^[:space:]]\'.
\'\\d\':匹配数字it is a synonym for `[0-9]\'.
\'\\D\':匹配非数字it is a synonym for `[^0-9]\'.

For example, \'\\brat\\b\' matches the separate word \'rat\', \'\\Brat\\B\' matches \'crate\' but not \'furry rat\'.

注意,\'\\b\'不仅仅只是简单的表示"匹配单词的边界空字符",它还要求它的左边或右边(且只能是一边)有单词字符。比如`.\\b`能匹配"abc/"中的c,但不能匹配最后那根斜线,因为"\\b"左边是非单词字符,右边是字符串结尾,也是非单词字符。但".\\b"能匹配"abc/x"中的"c"和"/"和"x"。

7.字符类,注意某些程序并不完全支持下列所有的字符类
\'[:alnum:]\' :same as \'[0-9A-Za-z]\'.
\'[:alpha:]\' :\'[:lower:]\' and \'[:upper:]\', same as \'[A-Za-z]\'.
\'[:lower:]\' :
\'[:upper:]\' :
\'[:digit:]\' :\'0 1 2 3 4 5 6 7 8 9\'.
\'[:xdigit:]\' :Hex digits: `0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f\'.

\'[:blank:]\' :space and tab.
\'[:space:]\' :tab, newline, vertical tab, form feed, carriage return, and space.
\'[:punct:]\' :Punctuation characters; this is \'! " # $ % & \' ( ) * + , - . / : ; < = > ? @ [ \\ ] ^ _ ` { | } ~\'.
\'[:print:]\' :\'[:alnum:]\', \'[:punct:]\', and space.
\'[:graph:]\' :Graphical characters: \'[:alnum:]\' and \'[:punct:]\'.

\'[:cntrl:]\' :Control characters. octal codes 000 through 037, and 177 (`DEL\').

8.同一个表达式中,被匹配过的字符无法被第二次匹配。因为正则的宗旨是:匹配了某字符或字符串后,紧跟着再匹配。
例如字符串"#c#",正则表达式"(#.)(.#)"无法匹配。
再例如字符串"#cc#",正则表达式"(#.)(.*)(.#)"能匹配成功,只不过第二个分组只能匹配空。

9."环视"锚定,即lookaround anchor(也称为"零宽断言",表示匹配的是位置,不是字符)。
以 (?= 替代左括号表示从左向右的顺序环视,例如(?=\\d)表示当前字符的右边是一个数字时就满足条件
以 (?<= 替代左括号表示从右向左的逆序环视,例如(?<=\\d)表示当前字符的左边是一个数字时就满足条件

  • 正向环视:(?=...)和(?!...),感叹号表否定,即无法匹配感叹号右边的字符时才捕获。
  • 逆向环视:(?<=...)和(?<!...)

逆向环视的表达式必须只能表示固定长度的字符串,例如(?<=word)或(?<=word|word)可以,但(?<=word?)不可以,因为?匹配0或1长度,长度不定。
在PCRE中,可重写为(?<=word|words),但perl中不允许,因为perl严格要求长度必须固定。


10.关于"环视"锚定,最需要注意的一点是匹配的结果不占用任何字符,它仅仅只是锚定位置。
例如:your name is longshuai MA 和 your name is longfei MA
使用(?=longshuai)将能锚定第一个句子中单词"longshuai"前面的空字符,但它的匹配结果是"longshuai"前的空白字符,
所以(?=longshuai)long才能代表"long"这几个字符串
所以仅对于此处的两个句子,long(?=shuai)和(?=longshuai)long是等价的

11.贪婪匹配、惰性匹配和占有优先匹配
默认情况下,对于重复次数的表达式都是贪婪匹配,表示尽可能多的匹配。
有些高级正则引擎支持惰性匹配,表示尽可能少的匹配,只要能满足条件就立即停止。

  • *、    +、    ?、     {M,N} :都是贪婪匹配(greedy)
  • *?、  +?、  ??、   {M,N}? :都是惰性匹配(lazy,Reluctant)
  • *+、  ++、  ?+、   {M,N}+ :都是占有优先匹配(possessive)

占有优先和固化分组是相同的,只要占有了就不再交换,不允许进行回溯。示例见下面的(?>...)固化分组方式

12.匹配模式

  • (?i):不区分大小写,可使用(?-i)取消该模式。例如"(?i)abc(?-i)cdB"只对中间的abc进行不区分大小写的匹配
    • 由于(?i)遇到闭括号就失效,可以将需要不区分大小写匹配的部分写入分组括号中,例如"((?i)abc)cdB",(?:(?i)abc)cdB=(?i:abc)cdB
  • (?x):extend模式,将忽略多个连续空格和注释符到行尾的字符
  • (?m):(multiline)多行模式,改变^和$的匹配模式。默认模式下,它们匹配字符串首部和尾部。此模式下:
    • ^将匹配字符串首部和换行符。若要仅匹配字符串首部,使用\\A。
    • $将匹配字符串尾部、换行符和换行符前的空字符。若要仅匹配字符串尾部和行尾,使用\\Z,若要仅匹配字符串尾部,使用\\z
  • (?s):(singleline或dotall)单行模式,改变"."的匹配模式,默认模式下,点"."无法匹配换行符,dotall模式下可以
  • (?U):lazy匹配模式。默认是greedy匹配。

13.强制字面解释:\\Q...\\E。该序列将其中间的所有字符强制解释为字面符号,强制性极强。
但perl和pcre有所不同。perl中,该序列中间可引用变量进行变量替换,而pcre中变量符号也被当作普通字符。

14.普通分组和捕获

  • (),$1,$2,$3,$4...有些地方使用\\1,\\2,\\3,\\4,sed中使用&表示所有匹配,perl中则使用$&
  • \\g1,\\g2,\\g3或\\g{1},\\g{2},\\g{3}。

其中$1,$2, ...用于正则外面,而"\\g1", "\\g2", ... 用于正则内部

15.命名分组和捕获

  • (?:...):非命名捕获,仅用于分组,不可用于引用,也称为非捕获型括号。例如"(1|one)(?:2|two)(3|three)",$1=(1|one),$2=(3|three)
  • (?<NAME>...):命名捕获,分组捕获后还命名,就像变量赋值一样。可以使用\\k<NAME>或\\k\'NAME\'或\\g{NAME}的方法来引用
  • (?>...):固化分组。一匹配成功就永不交回内容(用回溯的想法理解很容易)。

例如"hello world"可以被"hel.* world"进行匹配,但不能被"hel(?>.*) world"匹配。
因为正常情况下,".*"匹配到所有内容,然后回溯释放已匹配内容直到空格" "字符。而固化分组后,已匹配的内容绝不交回,也就无法回溯。

16. 重置匹配:\\K 用于重置匹配的位置。
比如,foot\\Kbar 匹配”footbar”,但是得到的匹配结果是 ”bar”。但是, \\K 的使用不会干预到子组内的内容, 比如 (foot)\\Kbar 匹配 ”footbar”,第一个子组内的结果仍然会是 ”foo”。

$ echo abc123abcfoo | grep -P -o \'(abc)123\\K\\g1foo\' 
abcfoo

17.要想对一个字符串匹配后取反。可以通过正向环视锚定取反来间接实现。
例如,"-a -3 ac c 3 b"中取出负数、正数和空格很简单,"-?[0-9]+|\\s"即可,但想要借此取反得到"-a ac c b",目前正则表达式只能通过(?!)的环视取反实现:"((?!-?[0-9]+|\\s).)*",外层括号表示右边不是正数、负数或空格的字符都匹配并进行分组,然后重复量词*,将连续的内容连接起来。
例如:

echo "-a -3 ac c 3 b" | grep -P \'((?!-?[0-9]+|\\s).)*\'

...

 

以上是关于pcre和正则表达式的误点的主要内容,如果未能解决你的问题,请参考以下文章

PHP 正则表达式(PCRE)

使用 cppcheck (pcre) 正则表达式搜索模式

什么是 PCRE 兼容语法? C# PCRE 兼容吗?

PHP 正则表达式(PCRE)

PCRE-正则库及用法

雷林鹏分享:PHP 正则表达式(PCRE)