java正则表达式与或⾮_正则表达式的与或⾮
我们使⽤正则表达式,熟练掌握各种功能和结构只是⼿段,解决实际的问题才是真正的⽬的。要解决真正的问题,就必须有解决问题的思路,正则表达式的功能,说到底,可以归纳为三种逻辑,为了表述⽅便,我们分别称为与、或、⾮。
逻辑关系
说明
与
在某个位置,某些元素(字符、字符组或者⼦表达式)必须出现
或
在某个位置,某个元素或许不出现,或许不出现,或许长度不固定;要出现的,是某⼏个元素中的⼀个
⾮
在某个位置,某些元素不能出现
⼀般来说,正则表达式千变万化,总是这三种逻辑的组合。⽐如匹配双引号字符串:
"quoted string"
逻辑关系regex匹配
分析
与
⾸尾的双引号字符必须出现
或
两个双引号之间的字符个数是不确定的(如果是空字符串””,则两个双引号之间没有字符)
⾮
两个双引号之间不能出现双引号字符
再⽐如匹配html中的open-tag(⽐如
)
和close-tag(⽐如
):
逻辑关系
分析
与
⾸尾必须分别是,如果是close-tag,则
或
之间必须出现⾄少⼀个字符(<>不是⼀个合法的tag)
⾮
下⾯我们来讲解三种逻辑的对策。
与
“与”是正则表达式中最普通的逻辑关系。⼀般来说,如果正则表达式中的元素没有任何量词(quantifier,⽐如*、?、+)修饰,就
是“与”关系。⽐如『
不过“与”的情况并没有这么简单,有时候,“必须出现”的是若⼲个元素,或者说,⼏个元素必须同时出现,但它们之间并不相连,这是⾮常容易犯错的时候,不过现在我们不举具体的例⼦,稍晚⼀点再说。
或
“或”是正则表达式中最灵活的逻辑关系。正则表达式能应对各种不同的⽂本,“或”功能不可或缺。
如果“或”的意思是,元素可以出现,也可以不出现,或者出现的次数不确定,可以⽤量词来表⽰“或”关系。⽐如表达式『a?』,表⽰在此处,字符a可以出现,也可以不出现;表达式『(ab)+』,表⽰在此处,字符串ab必然要出现1次,也可以出现⽆限多次。
如果“或”的意思是,可以出现的是某⼏个元素中的⼀个,则应该使⽤字符组或者多选结构。当元素都是单个字符时,就应该使⽤字符组『[…]』:⽐如匹配单词cat或者cut,除去开头的a、结尾的t是固定的,之中“或许出现a,或许出现u”,所以应当使⽤字符组『[au]』,整个正则表达式就是『c[au]t』。
当元素不只单个字符(只要有⼀个元素不只单个字符)时,就应该使⽤多选结构『(…|…)』:⽐如不但要匹配单词cat或者cut,还要匹配单词chart、conduct和court,出去开头的a、结尾的t是固定的,之中“或许出现a,或许出现u,或许出现har,或许出现onduc,或许出现our”,这时候就应该使⽤多选结构『(a|u|har|onduc|our)』,整个正则表达式就是
『c(a|u|har|onduc|our)t』。
当然,多选分⽀也可以表⽰字符组,⽐如『[au]』就可以表⽰为『(a|u)』,两者的功能是完全等价的,但是字符组的效率更⾼,也更直观(毕竟,⼤家都习惯了简单的『[au]』,⽽看到『(a|u)』则多半要想⼀想。
在实践中,“与”和“或”经常同时出现,⽽且关系不那么简单,下⾯举⼀个例⼦说明:为了隐藏真实的结构,我们需要⽤URL进⾏伪装,⽐如这个URL pattern:/foo/bar_tmp.php。
在真正的系统⾥,foo是模块名,bar是控制器名,tmp是⽅法名。合法的URL并不要求3个名字每次都出现,可以只出现控制器名(/foo),也可以只出现控制器名和模块名(/foo/bar.php),也可以3者都出现(/foo/bar_tmp.php)。
这⾥的模块名、控制器名、⽅法名,都可以⽤『[a-z]+』匹配,这⾥为说明⽅便,我们暂且⽤foo、bar、tmp代替对应的表达式。初看起来,这个表达式只包含“与”和“或”两种关系:
逻辑关系
分析
与
/foo必须出现
或
/bar、_tmp、.php都是可选出现的
所以,正则表达式是『/foo(/bar)?(_tmp)?(\.php)?』。
这个表达式确实可以匹配/foo、/foo/bar.php和/foo/bar_tmp.php,但是,它也可以匹配/foo_tmp、/foo/bar_tmp等形式,虽然这些形式并不是合法的。
仔细研究之后,我们发现,“与”和“或”的关系并没有那么简单,⽽应该是这样的:
逻辑关系
分析
与
/foo必须出现
或
/bar和.php是可选出现的,但必须同时出现,或同时不出现(与)
在/bar和.php都出现的前提下,_tmp才可以出现(或)
/foo必须出现,这很好表⽰,暂且不去管它;/bar和.php如果出现,必须同时出现,所以它们应该作为⼀个元素,写作『(/bar.php)』;整个元素可选出现,所以给它添加量词,得到『(/bar.php)?』;最后,在/bar和.php都出现的前提下,_tmp才可以出现,所以将
『(_tmp)?』填充到『(/bar.php)?』,得到『(/bar(_tmp)?.php)?』,最后加上开头的/foo,整个表达式就是『(/bar(_tmp)?.php)?』。到此,整个关系终于完整表达出来,表达式也不会发⽣错误匹配。
⾮
“⾮”是正则表达式中最难处理的逻辑关系。因为没有直接对应的结构,“⾮”的处理⽐较吃⼒。
最简单的“⾮”,意思是此处不能出现某个字符,这⼀点通常很直观,似乎⽤排除型字符组『[^…]』就可以解决。⽐如双引号字符串的匹配,⾸尾两个双引号很容易匹配,其中的内容肯定不是双引号(暂时不考虑转义的情况),所以可以⽤『[^"]』表⽰即可,其长度不确定,所以⽤*来限定,所以整个表达式就是『"[^"]*"』,⾮常简单。
但是,事情果真都如此简单吗?我们仍然举cat和cut的例⼦,如果仍然希望匹配c开头、t结尾的单词,但不希望匹配cut,可以写成
『c[^u]t』,是否就可以了?
这个表达式的意思是:最开头的字母是c,之后是⼀个不为u的字符,之后是t。没错,它确实不会匹配cut,也可以匹配cat。但是,chart、conduct、court等等,它也没法匹配,因为[^u]的意思是:匹配⼀个不是u的字符。
那么,把『[^u]』改成『[^u]+』好了,这样应该就可以解决问题了。但是真的如此吗?『[^u]+』的意思是,⼀个或若⼲(最多到⽆穷)个字符,但每⼀个字符都不能是u。所以,尽管『c[^u]+t』能匹配cat和chart,却不能匹配conduct和court。
看来,“⾮”真是⽐较难对付,让⼈⾮常纠结。好在,也不是没有办法解决它。回复到与-或-⾮的观点,分析要实现的功能:
逻辑关系
分析
与
以c开头,以t结尾
或
c和t之间可以出现的字母必须多于⼀个,没有上限
⾮
c和t之间不能只有⼀个字符u
如果只考虑“与”和“或”两个逻辑,表达式很好写,是『c[a-z]+t』,再把剩下的条件附加上去,就可以解决问题了。我们仔细
看“⾮”的条件:c和t之间不能只有⼀个字符u。既然『[^u]+』表达的并不是这个意思,我们不妨换⼀种表述法:在c之间的位置向后看,不能出现cut。这⼀点,正好对应否定顺序环视(positive look-ahead)
功能,『(?!cut)』就是⽤来进⾏这种判断的,它判断之后的字符串能不能由cut匹配,但并不真正真正进⾏匹配,也不会移动“当前位置”。所以我们将它放在表达式的最开头,得到『(?!cut)c[a-z]+t』。这个表达式的逻辑是:只有在当前位置右侧字符串不能由cut匹配的情况下,才从这⾥开始,向右尝试⽤c[a-z]+t。
如果我们更进⼀步,需要排除掉cat和cut,可以把否定顺序环视改为『(?!c[au]t)』。这样就能保证,匹配到的肯定不是cat或者cut。
更复杂⼀点,如果我们要验证这样⼀个字符串:它全部由⼩写字母构成,长度不超过12位,其中不能包含unfavored或者unwanted。也可以照章处理,先匹配“长度不超过12位”的⼩写字母『[a-z]{,12}』,然后写出匹配“不需要匹配内容”的正则表达式,
『(unfavored|unwanted)』,再⽤否定顺序环视将它“排除”即可,只是这次要注意,不能直接写『(?!(unfavored|unwanted))』,因为它只能排除『(unfavored|unwanted)』出现在字符串开头的情况,为了排除它出现在字符串中的情况,我们要把否定顺序环视改为『(?! [a-z]*(unfavored|unwanted))』,这样就确保完整的“排除”,整个表达式就是『(?![a-z]*(unfavored|unwanted))[a-z]{,12}』。
总结⼀下,正则表达式中的“⾮”,除去能⽤排除型字符组直接表⽰的,复杂⼀点的“⾮”逻辑都是按照这样的思路进⾏的:先⽤⼀个正则表达式准确匹配需要“排除”的字符串,再⽤环视功能排除掉它——“⾮”
确实是正则表达式中,最难处理的逻辑关系,好在它并不复杂,⽽且,除去⼀些⽐较古⽼的⼯具(⽐如Apache 1.3),现在各种⼯具和语⾔,基本都⽀持这种功能。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论