零宽断言

三月 14, 2022 [regexp, 零宽断言] #regexp #grep

零宽断言

正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。 零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,它们像 \b ^ $ < > 这样的锚定作用,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。 断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。 zero width assertion 主要分为四种:

  1. 先行断言 (?=exp)
  2. 后发断言 (?<=exp)
  3. 负向零宽先行断言 (?!exp)
  4. 负向零宽后发断言(?<!exp)

先行断言

表示匹配表达式前面的位置 如匹配ing前的单词

echo cooking singing | grep -oP '\w+(?=ing)'

^ 表示position0; (?=<)表示<之前的位置,在这里同样是position0

echo "<div>antzone" | grep -oP '^(?=<)<[^>]+>\w+'

一个有趣的例子

从这里可以看到先行断言用于对断言前的表达式配置位置

echo abc | grep -oP 'a(?=b)c'   # not match
echo abc | grep -oP 'a(?=b)bc'   # not match

后发断言

匹配表达式后面的位置 获取内核版本 去除变量值

echo kernel-5.6 kernel-2.6 kernel-3.8 | grep -oP '(?<=kernel-)[0-9.]+'
echo 'aaa bbb CD="123"' | grep -oP '(?<=CD=")\d+'

grep -P

grep -P alternative

echo "abc: 123 def 456 ghi: 789" | grep -Po ':\s*\K\d+'

负向零宽先行断言

匹配第二个ab, 因为第一个ab后为A-Z

echo "abZW863ab88" | grep -oP 'ab(?![A-Z])'

负向零宽后发断言

匹配ABC后的abc,数字后的不能匹配

echo 123abcABCabc | grep -oP '(?<![0-9])abc'

应用

HTML标签内容

(?<=<(\w+)>).(?=</\1>) 匹配不包含属性的简单HTML标签内里的内容。(?<=<(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是),然后是.(任意的字符串),最后是一个后缀(?=</\1>)。注意后缀里的/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了。整个表达式匹配的是之间的内容(再次提醒,不包括前缀和后缀本身)。

grep -oP :-perl-regexp 不支持不定长的零宽断言,这里就暂时写一个单字母tag例子

echo '<b>hello</b>' | grep -oP '(?<=<(\w{1})>).*(?=<\/\1>)'

总结

常用分组语法

  1. 捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
  2. (?exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
  3. (?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
  4. 零宽断言 (?=exp) 匹配exp前面的位置
  5. (?<=exp) 匹配exp后面的位置
  6. (?!exp) 匹配后面跟的不是exp的位置
  7. (?<!exp) 匹配前面不是exp的位置
  8. 注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读