正则表达式断言

作者: 歇歇 | 来源:发表于2017-11-22 00:26 被阅读172次

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

造成差异的原因就是:

式一中的\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,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
参考《正则指引》 - 于晟

相关文章

  • 浅谈正则表达式(中)

    本节我们继续接着浅谈正则表达式(上)来讲正则表达式的其他使用。 零宽断言 断言:俗话的断言就是“我断定什么什么”,...

  • 正则表达式中的断言(assertions)

    正则表达式中的断言(assertions) 1、什么是断言? 广义上理解,断言(assertions),从字面上理...

  • 正则表达式的先行断言(lookahead)和后行断言(lookb

    正则表达式的先行断言和后行断言一共有4种形式:(?=pattern) 零宽正向先行断言(zero-width po...

  • 【第八天】jmeter元件详解之断言

    断言组件用来对服务器的响应数据做验证。常用的断言是响应断言,支持正则表达式 1、BeanShell Asserti...

  • Java学习笔记 - 第026天

    每日要点 正则表达式 例子1:零宽正向先行断言、零宽负向先行断言、零宽正向后行断言、零宽负向后行断言 异常 自定义...

  • 正则表达式断言

    title: 正则表达式断言tags: [正则表达式]date: 2017-11-15 23:55:55 正则表达...

  • CocosCreator3.x开发笔记8: Invalid re

    在iOS下运行报错,是因为正则不支持 因为ios不支持零宽断言 什么是零宽断言? 粗略总结:零宽断言是正则表达式中...

  • 正则表达式 学习

    参考:正则表达式30分钟入门教程 元字符 字符转义 分枝条件 分组 向后引用 零宽断言 负向零宽断言 贪婪和懒惰 ...

  • 2018-05-18

    函数绑定器 静态断言 内联 CPP转义字符 正则表达式 //regex_match //判断匹配//rege...

  • 正则表达式断言

    需求 在使用正则表达式做白名单的过程中,碰到下面的需求 匹配http://www.example.com 底下所有...

网友评论

本文标题:正则表达式断言

本文链接:https://www.haomeiwen.com/subject/ttggvxtx.html