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}$

默认设置是 magicvim也推荐大家都使用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")}
};

常用的替换命令

:g/^$/d 删除所有的空白行

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值