正则表达式学习实例
20200715发现一个 notepad++ 的 bug:
正则模式下退出重新打开 notepad++,此时 Backward direction 变成可勾选状态, 若勾选上,正则表达式中的组号功能不可用
关于正则表达式,维基百科是这么说的:
正则表达式(英語:Regular Expression,常简写为regex、regexp或RE),又称正規表示式、正規表示法、規則運算式、常規表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器裡,正則表达式通常被用来检索、替换那些符合某个模式的文本。
简单的说就是如果你要查找或者替换大量的字符串或者文本文件,正则表达式就是非常高效的方法。
基础教程大家在网上搜下好了,我这里记录一下容易忘记或者不太常见的例子。
例1:
需求
去掉每行最后的 制表符、空格等空白符
查找
[\t ]+$
或者 \s+$
替换
[空]
例2:
需求
“check_begin:$ P E R D A C K ” (其中$之后的不是空格而是 0x00(NULL)
),将0x00去除
查找
\x00
替换
什么都不输入,即可去除 NULL
结果
“check_begin:$PERDACK”
说明
\xnn 表示ASCII代码中十六进制代码为nn的字符
例3:
需求
如下一整行字符串
CMediaControl.cpp:Active:beforeGetInitInfor.CMediaControl.cpp:GetInitInfor:off.CMediaControl.cpp:ActiveEnd.USBWindow.hpp:active USB2.CMediaControl.cpp:SP_GetInitInfor:beforeGetInitInfor.CMediaControl.cpp:GetInitInfor:off.
对其中“.”后面添加换行,但“.hpp”和“.cpp”除外。
查找
\.(?![hc]pp)
替换
\.\n
结果
CMediaControl.cpp:Active:beforeGetInitInfor.
CMediaControl.cpp:GetInitInfor:off.
CMediaControl.cpp:ActiveEnd.
USBWindow.hpp:active USB2.
CMediaControl.cpp:SP_GetInitInfor:beforeGetInitInfor.
CMediaControl.cpp:GetInitInfor:off.
说明
(?![hc]pp)
匹配“.”之后的非“hpp”“cpp”的情况。
引申
参见:正则表达式的先行断言(lookahead)和后行断言(lookbehind)
(?=exp)
: 匹配 exp 前面的位置(零宽正向先行断言(zero-width positive lookahead assertion))
(?<=exp)
: 匹配 exp 后面的位置(零宽正向后行断言(zero-width positive lookbehind assertion))
(?!exp)
: 匹配非 exp 前面的位置(零宽负向先行断言(zero-width negative lookahead assertion))
(?<!exp)
: 匹配非 exp 后面的位置(零宽负向后行断言(zero-width negative lookbehind assertion))
以上是四种 匹配位置 的断言,匹配位置的意思是不消耗字符。
而 (?:exp)
不仅匹配位置还消耗字符,所以在本例中 \.(?:[hc]pp)
和 \.[hc]pp
效果一样。
以下三种都消耗字符(就是说进行替换时这些字符会被替换掉)
(exp)
匹配exp,并捕获文本到自动命名的组里
(?<name>exp)
匹配exp,并捕获文本到名称为 name 的组里
(?:exp)
匹配exp,不捕获匹配的文本,也不给此分组分配组号
注意一下 (?:exp)
和其他两个的区别,不捕获不分配组号,这样 exp 所匹配的字符串不会缓存,可以提高正则表达式匹配的性能
例4:
需求
查找 c 代码中的整块注释(即以 /* XXX */
表示的块)
查找
\/\*(\*(?!\/)|[^*])*\*\/
替换
[空]
说明
这里需要注意一点,正则表达式多数情况下是贪婪匹配(即尽可能多的匹配字符),所以不能简单的使用如下写法
|
|
首先分成三个部分:\/\*
, (\*(?!\/)|[^*])*
, \*\/
开头和结尾部分好理解,就是查找 /*
开头, */
结尾的字串,每个符号前面都多个\
表示转义。
中间部分是这种形式:(P1|P2)*
, 其中 P1
和 P2
分别是 \*(?!\/)
和 [^*]
意思是匹配任意数量的 以 * 开始但是不以 / 结尾的字符 或者任意数量的 非 * 字符 。
这样就可以完整理解这个表达式了。
还有这样的写法 \/\*(?:\*(?!\/)|[^\*])*\*\/
比之前的写法多了 <code>(?:exp)</code> 的形式,参考上面的例子,好处是可以提高性能。
等等!关于贪婪和懒惰,这样其实也可以 \/\*(.|\r|\n)*?\*\/
其中 *?
表示重复任意次,但是尽可能少匹配。
例5:
需求
如下的代码块
|
|
若在一个cpp文件中有多处调用,现要去掉setMediaInfo函数的第二个参数,即改为如下形式
|
|
查找
m_pTitleBar->setMediaInfo\((.*?),(.*?),(.*?);
替换
m_pTitleBar->setMediaInfo\((\1),(\3);
说明
*?
表示懒惰匹配(即尽可能少的匹配字符),这个例子中有多个 ,
符号,所以需要懒惰匹配。
小括号内表示分组,从左到右按照123… 顺序分配组号,上面例子中函数三个参数分别分配了1,2,3三个组,替换时去掉第二组,留下了第一组和第三组,也就满足了需求。
PS:在 vscode 中使用组号是 $1
这样的。
例6:
需求
去掉含有某个字符串所在行(比如m_pTitleBar)
查找
^.*m_pTitleBar.*\r\n
注意:是去掉「所在行」,如果查找部分最后不是 \r\n
而是 $
,则只能去掉该行内容,该空白行还在。
PS: 神奇的是,在vs2008环境下,最后部分不是 \r\n
也不是 \r
而是 \n
。目前看似乎是vs2008不识别 \r
替换
[空]
延伸
若是要去掉不含有某字符串所在行
查找
^(?!.*m_pTitleBar).*\r\n
替换
[空]
注意不能写成 ^.*(?!m_pTitleBar).*\r\n
详情参考: 利用正则表达式排除特定字符串