零宽断言:用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。(摘自正则表达式30分钟入门教程)
分为正向和负向两种:
正向零宽断言:
(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc。
(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分:((?<=\d)\d{3})+\b,用它对1234567890进行查找时结果是234567890。
下面这个例子同时使用了这两种断言:(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
负向零宽断言:
前面我们提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果我们想查找这样的单词--它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:
\b\w*q[^u]\w*\b匹配包含后面不是字母u的字母q的单词。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。
零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
同理,我们可以用(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
举个例子:
#!/usr/bin/perl -w #使用正则表达式中"零宽断言"来匹配主机IP #负向零宽后发断言为 (?<!表达式) #author:pandaychen use strict; use warnings; my $grub_ip; my %ip_hash; #(?<![\d\.]) 环视,$1左侧不是数字或. my $ip_regex=qr{ (?<![\d\.]) ##环视,左侧不是数字或. ( ##捕获 (?:2[0-4]\d | 25[0-5] | [01]?\d\d? ) ##第一组 \. (?:2[0-4]\d | 25[0-5] | [01]?\d\d? ) ##第二组 \. (?:2[0-4]\d | 25[0-5] | [01]?\d\d? ) ##第三组 \. (?:2[0-4]\d | 25[0-5] | [01]?\d\d? ) ##第四组 ) (?![\d\.]) ##环视,$1右侧不是数字或. }x; while(<>) { if(m{$ip_regex}) #匹配ip_regex { #捕获成功 $grub_ip=$1; print $grub_ip,"\n"; $ip_hash{$grub_ip}++; } } my ($key,$value); #遍历Hash的常用做法 while( ($key,$value) = each %ip_hash ) { print "$key==>$value","\n"; #keys(%ip_hash); #返回Hash首址 sleep(2); }
本机运行结果为: