正则表达式零宽断⾔详解(?=,?=,?!,?!)
在使⽤正则表达式时,有时我们需要捕获的内容前后必须是特定内容,但⼜不捕获这些特定内容的时候,零宽断⾔就起到作⽤了
正则表达式零宽断⾔:
零宽断⾔是正则表达式中的难点,所以重点从匹配原理⽅⾯进⾏分析。零宽断⾔还有其他的名称,例如"环视"或者"预搜索"等等,不过这些都不是我们关注的重点。
⼀.基本概念:
零宽断⾔正如它的名字⼀样,是⼀种零宽度的匹配,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是⼀个位置⽽已。
作⽤是给指定位置添加⼀个限定条件,⽤来规定此位置之前或者之后的字符必须满⾜限定条件才能使正则中的字表达式匹配成功。
注意:这⾥所说的⼦表达式并⾮只有⽤⼩括号括起来的表达式,⽽是正则表达式中的任意匹配单元。
javascript只⽀持零宽先⾏断⾔,⽽零宽先⾏断⾔⼜可以分为正向零宽先⾏断⾔,和负向零宽先⾏断⾔。
代码实例如下:
实例代码⼀:
var str="abZW863";
var reg=/ab(?=[A-Z])/;
console.log(str.match(reg));
在以上代码中,正则表达式的语义是:匹配后⾯跟随任意⼀个⼤写字母的字符串"ab"。最终匹配结果是"ab",因为零宽断⾔"(?=[A-Z])"并不匹配任何字符,只是⽤来规定当前位置的后⾯必须是⼀个⼤写字母。
实例代码⼆:
var str="abZW863";
var reg=/ab(?[A-Z])/;
console.log(str.match(reg));
以上代码中,正则表达式的语义是:匹配后⾯不跟随任意⼀个⼤写字母的字符串"ab"。正则表达式没能匹配任何字符,因为在字符串中,ab的后⾯跟随有⼤写字母。
⼆.匹配原理:
上⾯代码只是⽤概念的⽅式介绍了零宽断⾔是如何匹配的。
下⾯就以匹配原理的⽅式分别介绍⼀下正向零宽断⾔和负向零宽断⾔是如何匹配的。
1.正向零宽断⾔:
代码实例如下:
var str="<div>antzone";
var reg=/^(?=<)<[^>]+>\w+/;
console.log(str.match(reg));
匹配过程如下:
⾸先由正则表达式中的""获取控制权,⾸先由位置0开始进⾏匹配,它匹配开始位置0,匹配成功,然后控制权转交给"(?=<)",由于""是零宽的,所以"(?=<)"也是从位置0处开始匹配,它要求所在的位置右侧必须是字符"<",位置0的右侧恰好是字符"<",匹配成功,然后控制权转交个"<",由于"(?=<)"也是零宽的,所以它也是从位置0处开始匹配,于是匹配成功,后⾯的匹配过程就不介绍了。
2.负向零宽断⾔:
代码实例如下:
var str="abZW863ab88";
var reg=/ab(?[A-Z])/g;
console.log(str.match(reg));
匹配过程如下:
⾸先由正则表达式的字符"a"获取控制权,从位置0处开始匹配,匹配字符"a"成功,然后控制权转交给"b",从位置1处开始
匹配,配字符"b"成功,然后控制权转交给"(?[A-Z])",它从位置2处开始匹配,它要求所在位置的右边不能够是任意⼀个⼤写字母,⽽位置的右边是⼤写字母"Z",匹配失败,然后控制权⼜重新交给字符"a",并从位置1处开始尝试,匹配失败,然后控制权再次交给字符"a",从位置2处开始尝试匹配,依然失败,如此往复尝试,直到从位置7处开始尝试匹配成功,然后将控制权转交给"b",然后从位置8处开始尝试匹配,匹配成功,然后再将控制权转交给"(?[A-Z])",它从位置9处开始尝试匹配,它规定它所在的位置右边不能够是⼤写字母,匹配成功,但是它并不会真正匹配ab后⾯的字符,所以最终匹配结果是"ab"。
下⾯补充有重复,可能断⾔⽅法名字有所不同,理解意思最重要,可以以补充三中的断⾔名为准。直接看补充三:
三、补充
零宽断⾔是正则表达式中的⼀种⽅法,正则表达式在计算机科学中,是指⼀个⽤来描述或者匹配⼀系列符合某个句法规则的字符串的单个字符串。
定义解释
零宽断⾔是正则表达式中的⼀种⽅法
正则表达式在计算机科学中,是指⼀个⽤来描述或者匹配⼀系列符合某个句法规则的字符串的单个字符串。在很多⽂本编辑器或其他⼯具⾥,正则表达式通常被⽤来检索和/或替换那些符合某个模式的⽂本内容。许多程序设计语⾔都⽀持利
⽤正则表达式进⾏字符串操作。例如,在Perl中就内建了⼀个功能强⼤的正则表达式引擎。正则表达式这个概念最初是由Unix中的⼯具软件(例如sed和grep)普及开的。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
零宽断⾔
⽤于查在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样⽤于指定⼀个位置,这个位置应该满⾜⼀定的条件(即断⾔),因此它们也被称为零宽断⾔。最好还是拿例⼦来说明吧:断⾔⽤来声明⼀个应该为真的事实。正则表达式中只有当断⾔为真时才会继续进⾏匹配。
(?=exp)也叫零宽度正预测先⾏断⾔,它断⾔⾃⾝出现的位置的后⾯能匹配表达式exp。⽐如\b(?=re)\w+\b,匹配以re开头的单词,如查reading a book.时,它会匹配reading。
var reg = new Regex(@"\w+(?=ing)");
var str = "muing";
Console.WriteLine(reg.Match(str).Value);//返回mu
(?<=exp)也叫零宽度正回顾后发断⾔,它断⾔⾃⾝出现的位置的前⾯能匹配表达式exp。⽐如\b\w+(?<=ing\b)会匹配以ing结尾的单词的前半部分(除了ing以外的部分),例如在查I am reading.时,它匹配read。
假如你想要给⼀个很长的数字中每三位间加⼀个逗号(当然是从右边加起了),你可以这样查需要在前⾯和⾥⾯添加逗号的部分:((?
=\d)\d{3})+\b,⽤它对1234567890进⾏查时结果是234567890。
下⾯这个例⼦同时使⽤了这两种断⾔:(?<=\s)\d+(?=\s)匹配以空⽩符间隔的数字(再次强调,不包括这些空⽩符)。
前⾯我们提到过怎么查不是某个字符或不在某个字符类⾥的字符的⽅法(反义)。但是如果我们只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?
例如,如果我们想查这样的单词--它⾥⾯出现了字母q,但是q后⾯跟的不是字母u,我们可以尝试这样:
\b\wq[^u]\w\b匹配包含后⾯不是字母u的字母q的单词。但是如果多做测试(或者你思维⾜够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[u]总要匹配⼀个字符,所以如果q是单词的最后⼀个字符的话,后⾯的[u]将会匹配q后⾯的单词分隔符(可能是空格,或者是句号或其它的什么),后⾯的\w\b将会匹配下⼀个单词,于是 \b\wq[^u]\w\b 就能匹配整个Iraq
fighting。\b\wq[^u]\w\b不会匹配Iraq fighting,只能匹配Iraq fighting中的aq f。⽽\b\w+q[^u]\w+\b才能匹配整个Iraq fighting。(2017-10-20修正,感谢RussellJX指正)
负向零宽断⾔能解决这样的问题,因为它只匹配⼀个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\wq(?!u)\w\b。
零宽度负预测先⾏断⾔(?!exp),断⾔此位置的后⾯不能匹配表达式exp。
例如:\d{3}(?!\d)匹配三位数字,⽽且这三位数字的后⾯不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
同理,我们可以⽤(?<!exp),零宽度负回顾后发断⾔来断⾔此位置的前⾯不能匹配表达式exp:(?<[a-z])\d{7}匹配前⾯不是⼩写字母的七位数字。
⼀个更复杂的例⼦:(?<=<(\w+)>).(?=<\/\1>)匹配不包含属性的简单HTML标签内⾥的内容。(<?=(\w+)>)指定了这样的前缀:被尖括号括起来的单
词(⽐如可能是),然后是.*(任意的字符串),最后是⼀个后缀(?=<\/\1>)。注意后缀⾥的\/,它⽤到了前⾯提过的字符转义;\1则是⼀个反向引⽤,引⽤的正是捕获的第⼀组,前⾯的(\w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是和之间的内容(再次提醒,不包括前缀和后缀本⾝)。
上⾯的看了有点伤脑筋啊。下⾯来点补充:
补充⼀:(复习正预测,正回顾,已经理解可以跳过)
断⾔⽤来声明⼀个应该为真的事实。正则表达式中只有当断⾔为真时才会继续进⾏匹配。
接下来的四个⽤于查在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样⽤于指定⼀个位置,这个位置应该满⾜⼀定的条件(即断⾔),因此它们也被称为零宽断⾔。最好还是拿例⼦来说明吧:
(?=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)匹配以空⽩符间隔的数字(再次强调,不包括这些空⽩符)。
补充⼆:(官⽅理解正预测)
零宽度正预测先⾏断⾔是什么呢,看msdn上的官⽅解释定义
(?= ⼦表达式) 零宽度正预测先⾏断⾔仅当⼦表达式在此位置的右侧匹配时才继续匹配。
例如,\w+(?=\d) 与后跟数字的单词匹配,⽽不与该数字匹配。
经典的例⼦:某单词以ing结尾,要获取ing前⾯的内容
var reg = new Regex(@"\w+(?=ing)");
var str = "muing";
Console.WriteLine(reg.Match(str).Value);//返回mu
以上是⽹上到处可见的例⼦,到这⾥或许你明⽩了,原来就是返回了exp表达式前⾯的内容。
再看下⾯的的代码
var reg = new Regex(@"a(?=b)c");
var str = "abc";
Console.WriteLine(reg.IsMatch(str));//返回false
为什么会返回false?
其实msdn官⽅定义已经说了,只是它说得很官⽅⽽已。这⾥需要我们注意⼀个关键点:**此位置。没错,是位置⽽不是字符。
**
那么结合官⽅定义和第⼀个例⼦来理解第⼆个例⼦:
因为a后⾯是b,则此时返回了匹配内容a(由第⼀个例⼦知道,只返回a不返回exp匹配的内容),此时a(?=b)c中的a(?=b)部分已经解决了,接下来要解决c的匹配问题了,此时匹配c要从字符串abc哪⾥开始呢,结合官⽅定义,就知道是从⼦表达的位置向右开始的,那么就是从b的位置开始,但b⼜不匹配a(?=b)c剩余部分的c,所以abc就不匹配a(?=b)c了。
那么如果要上⾯的进⾏匹配,正则应该如何写呢?
答案是:a(?=b)bc
当然,有⼈会说直接abc就匹配上了,还要这么折腾吗?当然不⽤这么折腾,只是为了说明零宽度正预测先⾏断⾔到底是怎么⼀回事?关于其它的零宽断⾔也是同⼀原理!
(最精简有⽤的)补充三:(看例⼦直接上⼿⽤)
(?=exp):零宽度正预测先⾏断⾔,它断⾔⾃⾝出现的位置的后⾯能匹配表达式exp。
匹配后⾯为_path,结果为product
'product_path'.scan
/(product)(?=_path)/
(?<=exp):零宽度正回顾后发断⾔,它断⾔⾃⾝出现的位置的前⾯能匹配表达式exp
匹配前⾯为name:,结果为wangfei
'name:wangfei'.scan
/(?<=name:)(wangfei)/
(?!exp):零宽度负预测先⾏断⾔,断⾔此位置的后⾯不能匹配表达式exp。
匹配后⾯不是_path
'product_path'.scan正则匹配是什么
/(product)(?!_path)/
匹配后⾯不是_url
'product_path'.scan
/(product)(?!_url)/
(?<!exp):零宽度负回顾后发断⾔来断⾔此位置的前⾯不能匹配表达式exp
匹配前⾯不是name:
'name:angelica'.scan
/(?<!name:)(angelica)/
匹配前⾯不是nick_name:
'name:angelica'.scan
/(?<!nick_name:)(angelica)/
再次提醒,如果由于同样表达式在不同地⽅的断⾔⽅法名(断⾔表达式叫法)不⼀致引起不适的话请以补充三中的为准。溜了溜了。谢谢⼤家的阅读。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论