先行断言和后行断言

”先行断言“指的是,x只有在y前面才匹配,必须写成x(?=y),"先行否定断言"指的是,x只有不在y前面才匹配,必须写成x(?!y)。

“后行断言”正好与“先行断言”相反,x只有在y后面才匹配,必须写成(?<=y)x。比如,只匹配美元符号之后的数字,要写成/(?<=\$)\d+/。”后行否定断言“则与”先行否定断言“相反,x只有不在y后面才匹配,必须写成(?<!y)x。比如,只匹配不在美元符号后面的数字,要写成(?<!\$)\d+。

之所以叫后行断言,是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。

  • 1、关于先行(lookahead)和后行(lookbehind):正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。

  • 2、关于正向(positive)和负向(negative):正向就表示匹配括号中的表达式,负向表示不匹配。

断言分四种:

  1. 先行断言 X(?=Y),也叫 零宽度正预测先行断言,当X的右边是Y时匹配成功;

  2. 后发断言 (<=Y)X,也叫 零宽度正回顾后发断言,当X的左边是Y时匹配成功;

  3. 负向零宽度先行断言 X(?!Y),当X的右边不是Y时匹配成功;

  4. 负向零宽后发断言 (?<!Y) X,当X的左边不是Y时匹配成功;

比如,'Win(?=2000)’ 匹配 Win2000, 但不匹配 WinXP。

XX预测断言,在右边;“预测”总是和“先行”成对出现;
XX回顾断言,在左边;“回顾”总是和“后发”成对出现;

$ cat foo | grep -P winwin200winxp200win95win200win95$ cat foo | grep -P 'win(?=2)'win200$ cat foo | grep -P 'win(?!2)'winxp200win95win200win95$ cat foo | grep -P 'win(?<=200)' # 正向回顾断言放在主体的右边没有用;只能用在左边$ cat foo | grep -P '(?<=200)win'200win200win95<chenx@PCOC f>$ cat foo | grep -P '(?<!200)win'win200winxp95win123456789101112131415161718192021

grep断言
https://blog.csdn.net/xuejinliang/article/details/52911149

零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像 \b ^ $ \< \> 这样的锚定作用,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。 断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。

其中零宽断言又分四种:

1)先行断言   也叫零宽度正预测先行断言(?=exp)   --  表示匹配表达式前面的位置

例如 [a-z]*(?=ing) 可以匹配 cooking 和 singing 中的 cook 与 sing 。

注意:先行断言的执行步骤是这样的先从要匹配的字符串中的最右端找到第一个 ing (也就是先行断言中的表达式)然后 再匹配其前面的表达式,若无法匹配则继续查找第二个 ing 再匹配第二个 ing 前面的字符串,若能匹配则匹配,符合正则的贪婪性。

例如: .*(?=ing) 可以匹配 "cooking singing" 中的 "cooking sing" 而不是 cook

2)后发断言   也叫零宽度正回顾后发断言(?<=exp)  --  表示匹配表达式后面的位置

例如(?<=abc).* 可以匹配 abcdefg 中的 defg

注意:后发断言跟先行断言恰恰相反 它的执行步骤是这样的:先从要匹配的字符串中的最左端找到第一个abc(也就是先行断言中的表达式)然后 再匹配其后面的表达式,若无法匹配则继续查找第二个 abc 再匹配第二个 abc 后面的字符串,若能匹配则匹配。

例如(?<=abc).* 可以匹配 abcdefgabc 中的 defgabc 而不是 abcdefg

3)负向零宽断言

负向零宽断言 (?!exp) 也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 (?!exp) 表示 "exp" 前面的位置,如果 "exp" 不成立 ,匹配这个位置;如果 "exp" 成立,则不匹配。同样,负向零宽断言也有“先行”和“后发”两种,负向零宽后发断言为 (?<!exp)

负向零宽后发断言(?<!exp)

负向零宽先行断言 (?!exp)

负向零宽断言要注意的跟正向的一样。

常用分组语法

分类 代码/语法 说明

捕获 (exp) 匹配exp,并捕获文本到自动命名的组里

(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)

(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号

零宽断言 (?=exp) 匹配exp前面的位置

(?<=exp) 匹配exp后面的位置

(?!exp) 匹配后面跟的不是exp的位置

(?<!exp) 匹配前面不是exp的位置

注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

例子:

cat file

aaa bbb CD="123" fd

要取出CD后面的值:

grep -oP '(?<=CD=")\d+' file

[解析]

以 CD=" 为后发断言,匹配它后面的多个数字。

cat file

Rx Optical Power: -5.01dBm, Tx Optical Power: -2.41dBm

要取出那几个分贝的值:

-5.01

-2.41

grep -oP '(?<=: ).*?(?=d)' file

[解析]

后发断言 ”: “ 后面的字符串,直到 d 字符前面的字符串,? 号的作用是避免贪婪匹配。

cat file

["check_ssh",ok],["check_eth",ok],["check_disk",ok],["check_swap",ok],["check_mem",ok],["check_hardware",false],["check_filesystem",false],["check_port",ok],["check_redis-server",ok],["check_login",ok]

取出[ ]中含 false 字样的内容:

check_hardware

check_filesystem

grep -Po '[^"]+(?=",false)'  file

总结:

断言的匹配方式有两种,一种是从最左边开始查找字符串,另外一种是从最右边开始查找字符串

1、cat file

aaa bbb CD="123" fd

要取出CD后面的值:

grep -oP '(?<=CD=")\d+' file

在此例子中,如果想要取出CD后面的数字字符的话,首先从左边开吃查找CD=”这个字符串,查找到之后取出后面跟的数字字符\d+。其中<=表示匹配字符串从字符串的最左边开始。

2、cat file

Rx Optical Power: -5.01dBm, Tx Optical Power: -2.41dBm

要取出那几个分贝的值:

-5.01

-2.41

grep -oP '(?<=: ).*?(?=d)' file

从左边开始向右边寻找,查找到: (冒号加上空格),这个字符串所在的为止,.*?意思是去掉grep的贪婪匹配,?=d意思是结束符号是d。去掉贪婪模式是什么意思呢,也就是如果匹配到d只有就结束第一次的匹配。

grep -oP '(?<=: ).*(?=d)' file

-5.01dBm, Tx Optical Power: -2.41

如果不加?,那么就会匹配到对后面的那个符号d。

3、

cat file

["check_ssh",ok],["check_eth",ok],["check_disk",ok],["check_swap",ok],["check_mem",ok],["check_hardware",false],["check_filesystem",false],["check_port",ok],["check_redis-server",ok],["check_login",ok]

取出[ ]中含 false 字样的内容:

check_hardware

check_filesystem

grep -oP '[^"]+(?=",false)' d

从右边开始查找false关键字,查找到之后,找到左边的不是以符号”开始的字符,这样就匹配到了false左边中括号内的字符串。

Perl常用转义字符:

\b  单词的边界

\w  表示[a-zA-Z0-9_]字母数字下划线

\W 表示不在[a-zA-Z0-9_]字母数字下划线中的字符

\d [0-9]中的一个数字

\D  不在[0-9]中的任意字符

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

分类 编程技术

正则表达式的先行断言和后行断言一共有 4 种形式:

  • (?=pattern) 零宽正向先行断言(zero-width positive lookahead assertion)

  • (?!pattern) 零宽负向先行断言(zero-width negative lookahead assertion)

  • (?<=pattern) 零宽正向后行断言(zero-width positive lookbehind assertion)

  • (?<!pattern) 零宽负向后行断言(zero-width negative lookbehind assertion)

这里面的 pattern 是一个正则表达式。

如同 ^ 代表开头,$ 代表结尾,\b 代表单词边界一样,先行断言和后行断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为"零宽"。所谓位置,是指字符串中(每行)第一个字符的左边、最后一个字符的右边以及相邻字符的中间(假设文字方向是头左尾右)。

下面分别举例来说明这 4 种断言的含义。

(?=pattern) 正向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配 pattern。

例如对 "a regular expression" 这个字符串,要想匹配 regular 中的 re,但不能匹配 expression 中的 re,可以用 re(?=gular),该表达式限定了 re 右边的位置,这个位置之后是 gular,但并不消耗 gular 这些字符。

将表达式改为 re(?=gular).,将会匹配 reg,元字符 . 匹配了 g,括号这一砣匹配了 e 和 g 之间的位置。

(?!pattern) 负向先行断言

代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配 pattern。

例如对 "regex represents regular expression" 这个字符串,要想匹配除 regex 和 regular 之外的 re,可以用 re(?!g),该表达式限定了 re 右边的位置,这个位置后面不是字符 g

负向和正向的区别,就在于该位置之后的字符能否匹配括号中的表达式。

(?<=pattern) 正向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配 pattern。

例如对 regex represents regular expression 这个字符串,有 4 个单词,要想匹配单词内部的 re,但不匹配单词开头的 re,可以用 (?<=\w)re,单词内部的 re,在 re 前面应该是一个单词字符。

之所以叫后行断言,是因为正则表达式引擎在匹配字符串和表达式时,是从前向后逐个扫描字符串中的字符,并判断是否与表达式符合,当在表达式中遇到该断言时,正则表达式引擎需要往字符串前端检测已扫描过的字符,相对于扫描方向是向后的。

(?<!pattern) 负向后行断言

代表字符串中的一个位置,紧接该位置之前的字符序列不能匹配 pattern。

例如对 "regex represents regular expression" 这个字符串,要想匹配单词开头的 re,可以用 (?<!\w)re。单词开头的 re,在本例中,也就是指不在单词内部的 re,即 re 前面不是单词字符。当然也可以用 \bre 来匹配。

对于这 4 个断言的理解,可以从两个方面入手:

  • 1、关于先行(lookahead)和后行(lookbehind):正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引擎会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。

  • 2、关于正向(positive)和负向(negative):正向就表示匹配括号中的表达式,负向表示不匹配。

对这 4 个断言形式的记忆:

  • 1、先行和后行:后行断言 (?<=pattern)、(?<!pattern) 中,有个小于号,同时也是箭头,对于自左至右的文本方向,这个箭头是指向后的,这也比较符合我们的习惯。把小于号去掉,就是先行断言。

  • 2、正向和负向:不等于 (!=)、逻辑非 (!) 都是用 !号来表示,所以有 ! 号的形式表示不匹配、负向;将 ! 号换成 = 号,就表示匹配、正向。

我们经常用正则表达式来检测一个字符串中包含某个子串,要表示一个字符串中不包含某个字符或某些字符也很容易,用 [^...] 形式就可以了。要表示一个字符串中不包含某个子串(由字符序列构成)呢?

用 [^...] 这种形式就不行了,这时就要用到(负向)先行断言或后行断言、或同时使用。

例如判断一句话中包含 this,但不包含 that

包含 this 比较好办,一句话中不包含 that,可以认为这句话中每个字符的前面都不是 that 或每个字符的后面都不是 that。正则表达式如下:

^((?<!that).)*this((?<!that).)*$或 ^(.(?!that))*this(.(?!that))*$

对于 this is runoob test 这句话,两个表达式都能够匹配成功,而 this and that is runoob test 都匹配失败。

在一般情况下,这两个表达式基本上都能够满足要求了。考虑极端情况,如一句话以 that 开头、以 that 结尾、that 和 this 连在一起时,上述表达式就可能不胜任了。 如 runoob thatthis is the case 或者 this is the case, not that 等。

只要灵活运用这几个断言,就很容易解决:

^(.(?<!that))*this(.(?<!that))*$^(.(?<!that))*this((?!that).)*$^((?!that).)*this(.(?<!that))*$^((?!that).)*this((?!that).)*$

这 4 个正则表达式测试上述的几句话,结果都能够满足要求。

上述 4 种断言,括号里的 pattern 本身是一个正则表达式。但对 2 种后行断言有所限制,在 Perl 和 Python 中,这个表达式必须是定长(fixed length)的,即不能使用 *、+、? 等元字符,如 (?<=abc) 没有问题,但 (?<=a*bc) 是不被支持的,特别是当表达式中含有|连接的分支时,各个分支的长度必须相同。之所以不支持变长表达式,是因为当引擎检查后行断言时,无法确定要回溯多少步。Java 支持 ?、{m}、{n,m} 等符号,但同样不支持 *、+ 字符。Javascript 干脆不支持后行断言,不过一般来说,这不是太大的问题。

先行断言和后行断言某种程度上就好比使用 if 语句对匹配的字符前后做判断验证。

以下列出 ?=、?<=、?!、?<!= 的使用

exp1(?=exp2):查找 exp2 前面的 exp1。

(?<=exp2)exp1:查找 exp2 后面的 exp1。

exp1(?!exp2):查找后面不是 exp2 的 exp1。

(?<!=exp2)exp1:查找前面不是 exp2 的 exp1。

参考链接:https://blog.51cto.com/cnn237111/749047

(0)

相关推荐