Vim
的正则表达式
转自博客http://qianjigui.javaeye.com/blog/368449,删除了一些不常用的东西,同时添加了<<学习vi和vim编辑器>>的部分内容.
毋庸多言,在vim
中正则表达式得到了十分广泛的应用。 最常用的/
和 :s
命令中,正则表达式都是不可或缺的。 下面对vim
中的正则表达式的一些难点进行说明。
关于magic
vim
中有个magic
的设定。设定方法为:
:set magic " 设置magic
:set nomagic " 取消magic
:h magic " 查看帮助
vim
毕竟是个编辑器,正则表达式中包含的大量元字符如果原封不动地引用(像perl
那样), 势必会给不懂正则表达式的人造成麻烦,比如 /foo(1)
命令, 大多数人都用它来查找foo(1)
这个字符串, 但如果按照正则表达式来解释,被查找的对象就成了 foo1
了。
于是,vim
就规定,正则表达式的元字符必须用反斜杠进行转义才行, 如上面的例子,如果确实要用正则表达式,就应当写成/foo\(1\)
。 但是,像. *
这种极其常用的元字符,都加上反斜杠就太麻烦了。 而且,众口难调,有些人喜欢用正则表达式,有些人不喜欢用……
为了解决这个问题,vim
设置了 magic
这个东西。简单地说, magic
就是设置哪些元字符要加反斜杠哪些不用加的。 简单来说:
magic (\m):除了
$ . * ^
之外其他元字符都要加反斜杠。
nomagic (\M):除了$ ^
之外其他元字符都要加反斜杠。
这个设置也可以在正则表达式中通过 \m \M
开关临时切换。 \m
后面的正则表达式会按照 magic
处理,\M
后面的正则表达式按照 nomagic
处理, 而忽略实际的magic
设置。
例如:
/\m.* # 查找任意字符串
/\M.* # 查找字符串 .* (点号后面跟个星号)
另外还有更强大的 \v
和 \V
。
\v
(即 very magic
之意):任何元字符都不用加反斜杠\V
(即 very nomagic
之意):任何元字符都必须加反斜杠
例如:
/\v(a.c){3}$ # 查找行尾的abcaccadc
/\m(a.c){3}$ # 查找行尾的(abc){3}
/\M(a.c){3}$ # 查找行尾的(a.c){3}
/\V(a.c){3}$ # 查找任意位置的(a.c){3}$
默认设置是 magic
,vim
也推荐大家都使用magic
的设置,在有特殊需要时,直接通过\v\m\M\V
即可。
对于菜鸟而言,默认的设置足够了,但是对于比较熟悉正则表达式的朋友来说,\v
自然是上上之选.
量词
本文下面使用的元字符都是 magic
模式(除了$ . * ^
之外其他元字符都要加反斜杠)下的,在very magic
模式下,只需要将\\
去掉即可.
vim | 意义 |
---|---|
* |
匹配0 个或多个(匹配优先) |
\+ |
匹配1 个或多个(匹配优先) |
\? 或\=
|
0 个或1 个(匹配优先),\? 不能在 ? 命令(逆向查找)中使用 |
\{n,m} |
匹配n 个到m 个(匹配优先),如\d{1, 3} 可以匹配1 到3 个数字,类似11 , 1 , 333
|
\{n,} |
最少n 个(匹配优先) |
\{,m} |
最多m 个(匹配优先) |
\{n} |
恰好n 个 |
在这里,有些东西需要说明一下,那就是上面的用于限定数量的元字符不单单可以用于字符,同时也可以用于模式,举个例子,下面的模式:
\(123\)\{2}
可以匹配123123
.
一些常用的元字符
元字符 | 说明 |
---|---|
. |
匹配任意一个字符,如p*p 可以匹配字符串pep , pip 或者pcp
|
[abc] |
匹配方括号中的任意一个字符。可以使用-表示字符范围 |
[a-z0-9] |
匹配小写字母和阿拉伯数字 |
[^abc] |
在方括号内开头使用^ 符号,表示匹配除方括号中字符之外的任意字符 |
\d |
匹配阿拉伯数字,等同于[0-9]
|
\D |
匹配阿拉伯数字之外的任意字符,等同于[^0-9]
|
\x |
匹配十六进制数字,等同于[0-9A-Fa-f]
|
\w |
匹配单词字母,等同于[0-9A-Za-z_]
|
\W |
匹配单词字母之外的任意字符,等同于[^0-9A-Za-z_]
|
\t |
匹配<TAB> 字符 |
\s |
匹配空白字符,等同于[ \t]
|
\S |
匹配非空白字符,等同于[^ \t]
|
\a |
所有的字母字符. 等同于[a-zA-Z]
|
\l |
小写字母[a-z]
|
\L |
非小写字母[^a-z]
|
\u |
大写字母 [A-Z]
|
\U |
非大写字幕[^A-Z]
|
元字符的转义:
\*
匹配 *
字符
\.
匹配 .
字符
\/
匹配 /
字符
\\
匹配\
字符
\[
匹配 [
字符
表示位置的符号
位置元字符 | 含义 |
---|---|
$ |
匹配行尾,如here:$ 只会匹配出位于一行结尾的here: . |
^ |
匹配行首,如^Part 只会匹配出位于一行开头的Part . |
\< \>
|
会匹配出以某些字符开头的(\< )或结尾(\> )的单词.\<ac 只会匹配出以ac 开头的单词,如action ,而ac>/ 只会匹配出以ac 结尾的单词,如maniac .\<action\> 会匹配出action 这个单词.单词的开头和结尾,是用标点符号或空格来分隔的. |
替换变量
在正规表达式中使用 \(
和 \)
符号括起正规表达式,即可在后面使用\1
、\2
等变量来访问 \(
和 \)
中的内容。这种形式实际上是将\(
与\)
中的模式保存到了特殊的空间(称之为"保留缓冲区").这种方法可以保存任意一行中的9
个模式.
举个例子,对于下面的模式:
\(That\) or \(this\)
会将That
存放到缓冲区1
中,而将this
保存到保留缓冲区2
中,这些保留的模式在以后可以用\1
到\9
的序列重新排列,例如,如果要将That or this
改成this or That
,可以键入:
:%s/\(That\) or \(this\)/\2 or \1/
也可以实现在搜索或者替换字符串时使用\n表示法:
:%s\(abcd\)\1/alphabet-soup/
可以将abcdabcd
替换为alphabet-soup
.这里需要特别注意一下,\0
表示我们所匹配的所有内容.
非贪婪匹配
非贪婪匹配也是正则表达式中一个非常强大的特性,我这里稍微来记录一下vim
中非贪婪匹配的语法.
假设我有这样一段文本:
map<wstring, wstring> grammarTokens = {
{L"_LPAR", L"\\("},
{L"_RPAR", L"\\)"},
{L"_LBRA", L"\\["},
{L"_RBRA", L"\\]"},
{L"OP", L"[+?]"},
{L"_COLON", L":"}, // 冒号
{L"_OR", L"\\|"},
{L"_DOT", L"\\."},
{L"RULE", L"!?[_?]?[a-z][_a-z0-9]*"}, // 用于表示普通的规则
{L"TOKEN", L"_?[A-Z][_A-Z0-9]*"},
{L"REGEXP", L"/(?!/)(\\/|\\\\|[^/\n])*?/i?"},
{L"_NL", L"(\r?\n)+\s*"},
{L"WS", L"[\t]+"},
{L"COMMENT", L"//[^\n]*"},
{L"_TO", L"-->"},
{L"_IGNORE", L"%ignore"},
{L"_IMPORT", L"%import"}
};
上面的这段c++
代码片段实际上是存在错误的,要将所有的字符都变成这样wstring(L"xxxx")
,才能消除错误,所以,我们想到了,正则表达式正好可以用来干这个事情.
最开始的时候,我使用的是这种语法:
:%s/\(L".*"\)/wstring(\1)/g
结果很有意思:
map<wstring, wstring> grammarTokens = {
{wstring(L"_LPAR", L"\\(")},
{wstring(L"_RPAR", L"\\)")},
{wstring(L"_LBRA", L"\\[")},
{wstring(L"_RBRA", L"\\]")},
{wstring(L"OP", L"[+?]")},
{wstring(L"_COLON", L":")}, // 冒号
{wstring(L"_OR", L"\\|")},
{wstring(L"_DOT", L"\\.")},
{wstring(L"RULE", L"!?[_?]?[a-z][_a-z0-9]*")}, // 用于表示普通的规则
{wstring(L"TOKEN", L"_?[A-Z][_A-Z0-9]*")},
{wstring(L"REGEXP", L"/(?!/)(\\/|\\\\|[^/\n])*?/i?")},
{wstring(L"_NL", L"(\r?\n)+\s*")},
{wstring(L"WS", L"[\t]+")},
{wstring(L"COMMENT", L"//[^\n]*")},
{wstring(L"_TO", L"-->")},
{wstring(L"_IGNORE", L"%ignore")},
{wstring(L"_IMPORT", L"%import")}
};
这个显然是超乎我们预期的,原因在于正则表达式中.*
是贪婪匹配,什么意思呢,也就是说,这个表达式会一直向前匹配,匹配尽可能多的文本.
以{L"_LPAR", L"\\("}
这一行为例,用\(L".*"\)
来进行匹配的时候,L
匹配L
, "
匹配"
,然后.
可以匹配任意的字符,*
代表重复零次或者多次,因此,这里匹配了_LPAR
,虽然下一个"
可以和正则式的"
相匹配,如果此时停下来,是完全合理的,但是所谓的贪婪,就体现在了这里,我要一直尝试,一定要匹配更多的字符,所以继续前进,.*
匹配了_LPAR",L"\\(
,一直到下一个"
,正则表达式发现如果我继续用.*
来匹配掉"
的话,那么在这一行,我的匹配会失败,所以不能继续了,所以用正则表达式中的"
匹配"
,匹配成功.
你可能会疑问,为什么.*
不匹配到下一行,下下行,我只能说,vim
的正则表达式是一行一行进行匹配的.
所以人们为了避免这种情况,提出了一个非贪婪匹配的概念,核心是,匹配尽可能少的字符.
所以在这里,我们要将其替换为非贪婪匹配,非贪婪匹配的语法很奇葩,是这样的\{-}
,我们要将前面的.*
变成.\{-}
,所以命令变成了:
:%s/\(L".\{-}"\)/wstring(\1)/g
或者使用very magic
模式,也可以达到同样的效果:
:%s/(L.{-})/wstring(\1)/g
结果非常漂亮:
map<wstring, wstring> grammarTokens = {
{wstring(L"_LPAR"), wstring(L"\\(")},
{wstring(L"_RPAR"), wstring(L"\\)")},
{wstring(L"_LBRA"), wstring(L"\\[")},
{wstring(L"_RBRA"), wstring(L"\\]")},
{wstring(L"OP"), wstring(L"[+?]")},
{wstring(L"_COLON"), wstring(L":")}, // 冒号
{wstring(L"_OR"), wstring(L"\\|")},
{wstring(L"_DOT"), wstring(L"\\.")},
{wstring(L"RULE"), wstring(L"!?[_?]?[a-z][_a-z0-9]*")}, // 用于表示普通的规则
{wstring(L"TOKEN"), wstring(L"_?[A-Z][_A-Z0-9]*")},
{wstring(L"REGEXP"), wstring(L"/(?!/)(\\/|\\\\|[^/\n])*?/i?")},
{wstring(L"_NL"), wstring(L"(\r?\n)+\s*")},
{wstring(L"WS"), wstring(L"[\t]+")},
{wstring(L"COMMENT"), wstring(L"//[^\n]*")},
{wstring(L"_TO"), wstring(L"-->")},
{wstring(L"_IGNORE"), wstring(L"%ignore")},
{wstring(L"_IMPORT"), wstring(L"%import")}
};
错误消除.
环视,或者说正向预查,反向预查
在各种常用工具对比中,我看到vim
是支持计数的,而且似乎大部分常用的正则元字符都与perl
兼容,比如\s
,\d
,\D
,\w
,\W
, <
>
。但vim
不支持\b
,即单词边界。另外,vim
中比较麻烦的是它似乎支持的是BRE
(基本正则表达式,posix
定义的),BRE
中所有括号都不是元字符,因为作为元字符的是\(
,\{
。比如vim
中匹配连续3
个9
,你得用9\{3\}
,原来我一直以为不支持,但我还是觉得麻烦了一点,grep
默认也是使用的这种BRE
。
与perl
相比,vim
将(?
换成了\@
,并且这个符号应该跟在匹配模式的后边。下面是一组对比:
vim | Perl | 意义 | 例子 |
---|---|---|---|
\@= |
(?= |
顺序环视 | 查找后面是sql 的my : /my\(sql\)\@=
|
\@! |
(?! |
顺序否定环视 | 查找后面不是sql 的my : /my\(sql\)\@!
|
\@<= |
(?<= |
逆序环视 | 查找前面是my 的sql : /\(my\)\@<=sql
|
\@<! |
(?<! |
逆序否定环视 | 查找前面不是my 的sql : /\(my\)\@<!sql
|
\@> |
(?> |
固化分组 | |
\%(atom\) |
(?: |
非捕获型括号(此分组不捕获,可以理解为不算在分组信息中) |
:%s/\%(my\)sql\(ok\)/\1 这个命令会将mysqlok 替换为 ok ,由于my 为捕获在分组中,故组中\1 为ok 。 |
正如前面所说的,如果使用very magic
模式的话,这些命令都将大大简化,因为我们可以省略掉大量的转义字符,以:%s/\%(my\)sql\(ok\)/\1
为例,它可以替换成%s/\v%(my)sql(ok)/\1
,是不是清晰很多了呢.
常用的替换命令
删除所有的空白行
:g/^$/d