正则表达式断言


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结束的字符串,例如:luwuerlu fd --r,不能匹配nb luwuerlu 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,ES2018支持
  • 否定逆序环视(反向否定断言)positive-lookahead: ?<!pattern,ES2018支持

逆序环视兼容性:https://caniuse.com/?search=%20regular%20expressions%20lookbehind

逆序环视兼容性

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

('三体是刘慈欣创作的系列长篇科幻小说,由《三体》、《三体Ⅱ·黑暗森林》、《三体Ⅲ·死神永生》组成。').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
  • 参考《正则指引》 - 于晟
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容