零宽断言
三月 14, 2022 [regexp, 零宽断言] #regexp #grep零宽断言
正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。 零宽断言用于查找在某些内容(但并不包括这些内容)之前或之后的东西,它们像 \b ^ $ < > 这样的锚定作用,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。 断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。 zero width assertion 主要分为四种:
- 先行断言 (?=exp)
- 后发断言 (?<=exp)
- 负向零宽先行断言 (?!exp)
- 负向零宽后发断言(?<!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
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>)'
总结
常用分组语法
- 捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
- (?
exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) - (?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
- 零宽断言 (?=exp) 匹配exp前面的位置
- (?<=exp) 匹配exp后面的位置
- (?!exp) 匹配后面跟的不是exp的位置
- (?<!exp) 匹配前面不是exp的位置
- 注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读