python正则表达式断言_正则表达式断言

title: 正则表达式断言

tags: [正则表达式]

date: 2017-11-15 23:55:55

正则表达式大多数结构匹配的文本会出现在最终的匹配结果中,但也有些结构并不真正匹配文本,而只是负责判断某个位置左/右侧是否符合要求,这种结构被称为断言(assertion)。常见的断言有三类: 单词边界、行起始/结束位置、环视。本文主要简单阐述对三类断言的理解。

单词边界

单词边界顾名思义,是指单词字符(\w)能匹配的字符串的左右位置。在javascript、php、python 2、ruby中,单词字符(\w)等同于[0-9a-zA-Z],所以在这些语言中,给定一段文本可以用\b\w+\b把所有单词提取出来。

// 例如

('Love is composed of a single soul inhabiting two bodies.').match(/\b\w+\b/g)

return ["Love", "is", "composed", "of", "a", "single", "soul", "inhabiting", "two", "bodies"]

这里值得注意的是,有些单词例如e-mail和组合词I'm这样的,\b\w+\b是无法匹配的。如要匹配,可根据需求修改为\b['-\w]\b

单词边界记为\b,它能匹配的位置:一边是单词字符\w,一边是非单词字符\W。

与单词边界对应的是非单词边界\B,两者关系类似\w与\W、\d与\D。

这里注意,非单词边界(\B)和单词字符(\w)是不一样的,因为前者是断言,而后者是普通匹配。例如:

// 式一

String(1234567890).replace(/(?=(\B)(\d{3})+$)/g, ',')

=> 1,234,567,890

// 式二

String(1234567890).replace(/(?=(\w)(\d{3})+$)/g, ',')

=> ,123,456,7890

// 附加常用例子,20180911格式化为2018-09-11

'20180911'.replace(/(?=\B(\d{2})+$)/g, '-').replace(/-/, '')

=>2018-09-11

造成差异的原因就是:

式一中的\B匹配边界(是断言)。第一次匹配时,在1234567890中数字1的前方时,会环视后方进行肯定断言(?=):后方必须是满足两个pattern才通过。第一个pattern(\B)在数字1的前方匹配成功;故继续在此位置匹配第二个pattern(\d{3})+$,发现123456789之后并不是结束符(结束符和开始符也是断言,下文讲述),故匹配失败。开始第二次匹配,从数字1和数字2的中间开始...最后会匹配成功三个位置:1和2之间、4和5之间、7和8之间,再被,替换,故得到结果。

同理,式二在第一次匹配时,在数字1的前方环视后方进行肯定断言:后方必须是满足两个pattern才通过。第一个pattern(\w)在数字1的前方匹配成功,并将匹配位置移动到1和2之间;然后继续匹配第二个pattern(\d{3})+$...第一次匹配成功,故数字1前方的断言是成功的,标记该位置...最后得到三个位置:1前方、3和4之间、6和7之间,再被,替换,故得到结果。

所以\B只是去判断该位置左右是否只有一边有单词字符,另一边不是单词字符,且在匹配成功时,不会导致匹配位置发生改变。说起来算是一种判断吧~

这种只是匹配某个位置而不是文本的元字符,在正则中也被称为锚点。下文继续介绍常见锚点之二:行起始/结束位置

行起始/结束位置

^与$分别表示(行)起始位置和(行)结束位置,比如正则表达式/^lu.*r$/只能匹配的lu开始并以r结束的字符串,例如:luwuer、lu fd --r,不能匹配nb luwuer、lu fd --rb等。

其实行起始/结束位置断言,常用在正则表达式开启多行模式(Multiline Mode)的情况下。例如:

注:js开启多行模式的方式,在正则表达式后添加附加参数m,同全局匹配g

('first line\nsecond line\nlast line').match(/^\w+/gm)

return ["first", "second", "last"]

既然是多行匹配,这里说说如何划分行。

在编辑文本时,敲回车键就向文本输入了行终止符(line terminal),表示结束当前行。这里只需注意,敲入回车时向文本中输入的行终止符在主流平台上是有差别的:

Windows的行终止符是\r\n

UNIX/Linux/Mac OS的行终止符是\n

不过正则的行起始/结束位置断言都是可以识别的哈~

环视

环视是指在某个位置向左/向右看,保证其左/右位置必须出现某类字符(包括单词字符\w和非单词字符\W),且环视也同上两个断言,只是做一个判断(匹配一个位置,本身不匹配任何字符,但又比上两个断言灵活)。也有人称环视为零宽断言。

环视分为四种:

肯定顺序环视(正向肯定断言)positive-lookahead: ?=pattern

否定顺序环视(正向否定断言)positive-lookahead: ?!pattern

肯定逆序环视(反向肯定断言)positive-lookahead: ?<=pattern,js不支持

否定逆序环视(反向否定断言)positive-lookahead: ?<=pattern,js不支持

比如我们要匹配一串文字中包含在书名号《》中的书名,如不考虑环视可能需要如下实现:

('三体是刘慈欣创作的系列长篇科幻小说,由《三体》、《三体Ⅱ·黑暗森林》、《三体Ⅲ·死神永生》组成。').match(/《.*?》/g).join(',').replace(/[《》]/g, '').split(',')

return ["三体", "三体Ⅱ·黑暗森林", "三体Ⅲ·死神永生"]

正则默认是婪模模式(在整个表达式匹配成功的前提下,尽可能多的匹配),开启非贪婪模式(在整个表达式匹配成功的前提下,尽可能少的匹配)的方法:在贪婪量词{m,n}、{m,}、?、*、+后加上一个?号,例如+?

而在使用环视时会更简单:

('三体是刘慈欣创作的系列长篇科幻小说,由《三体》、《三体Ⅱ·黑暗森林》、《三体Ⅲ·死神永生》组成。').replace(/《/g,'\n').match(/^.*?(?=》)/gm)

return ["三体", "三体Ⅱ·黑暗森林", "三体Ⅲ·死神永生"]

hah,例子没举好,似乎也没简单多少...当然最主要的原因是js不支持逆序环视啦啦啦

再举例,匹配6位数字构成的字符串:

// 无环视

'http://luwuer.com/629212/1234567890'.match(/[^\d]\d{6}[^\d]/g).join('').match(/\d{6}/g)

return ["629212"]

// 环视

'http://luwuer.com/629212/1234567890'.match(/(?!\d).\d{6}(?!\d)/g).join('').match(/\d{6}/g)

return ["629212"]

其实环视在js中更多的是与replace函数组合,就像在单词边界一节中最后的例子。

原文 不要误会,就是我写的 /keai

参考《正则指引》 - 于晟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值