1.7. 搜索、替换和正则表达式大家应该都已经知道 Vim 里使用“/模式”(或“?模式”)进行搜索,使用“:s/模式/字符串/标志”进行替换,其中的“模式”是一个正则表达式。关于正则表达式,不熟悉的话可以边用边学,本节也不打算对 Vim 的正则表达式作完整的阐述(那可能可以专门写一本小册子了),而只抛砖引玉式地给出一些有用的例子加以说明,以及一些实用技巧。
先说一点点搜索。搜索里最最有用的一个快捷方式是“*”(向下完整匹配光标下的单词)。把光标移动到你要搜索的词(变量名、函数名等)上,比如“test”,然后按“*”,Vim 将自动产生一个对“\<test\>”(参见“:help /\<”和“:help /\>”)的搜索,也就是说,搜索完整的单词“test”。不要小看这个技巧,它经常可以大幅度地提高搜索的速度。事实上,这是 Vim 网站上公布的第 1 号技巧,也是被评价最高的技巧。相似的技巧还有“#”(向上完整匹配光标下的单词)、“g*”(向下部分匹配光标下的单词)等,请自行查看(“:help #”等)。
Vim 在搜索和替换时会对匹配成功的文本进行加亮,在已经完成搜索和替换任务后,这种加亮有时反而会妨碍显示。Vim 专门提供一个命令取消这种加亮(直到用户再一次使用搜索或替换命令):“:nohlsearch”。建议用户创建一个键盘映射(key mapping)加入到 .vimrc 中,如:
1
| nmap <F2> :nohlsearch<CR>
|
以上命令表示,在正常模式下按 F2 键相当于输入“:nohlsearch”后面跟一个回车,即取消搜索加亮显示。
再看几个搜索替换的实用例子。
- 去掉所有的行尾空格:“:%s/\s\+$//”。“%”表示在整个文件范围内进行替换,“\s”表示空白字符(空格和制表符),“\+”对前面的字符匹配一次或多次(越多越好),“$”匹配行尾(使用“\$”表示单纯的“$”字符);被替换的内容为空;由于一行最多只需替换一次,不需要特殊标志。这个还是比较简单的。
- 去掉所有的空白行:“:%s/\(\s*\n\)\+/\r/”。这回多了“\(”、“\)”、“\n”、“\r”和“*”。“*”代表对前面的字符(此处为“\s”)匹配零次或多次(越多越好;使用“\*”表示单纯的“*”字符),“\n”代表换行符,“\r”代表回车符,“\(”和“\)”对表达式进行分组,使其被视作一个不可分割的整体。因此,这个表达式的完整意义是,把连续的换行符(包含换行符前面可能有的连续空白字符)替换成为一个单个的换行符。唯一很特殊的地方是,在模式中使用的是“\n”,而被替换的内容中却不能使用“\n”,而只能使用“\r”。原因是历史造成的,详情如果有兴趣的话可以查看“:help NL-used-for-Nul”。
- 去掉所有的“//”注释:“:%s!\s*//.*!!”。首先可以注意到,这儿分隔符改用了“!”,原因是在模式或字符串部分使用了“/”字符,不换用其他分隔符的话就得在每次使用“/”字符本身时写成“\/”,上面的命令得写成“:%s/\s*\/\/.*//”,可读性较低。命令本身倒是相当简单,用过正则表达式的人估计都知道“.”匹配表示除换行符之外的任何字符吧。
- 去掉所有的“/* */”注释:“:%s!\s*/\*\_.\{-}\*/\s*! !g”。这个略有点复杂了,用到了几个不太常用的 Vim 正则表达式特性。“\_.”匹配包含换行在内的所有字符;“\{-}”表示前一个字符可出现零次或多次,但在整个正则表达式可以匹配成功的前提下,匹配的字符数越少越好;标志“g”表示一行里可以匹配和替换多次。替换的结果是个空格的目的是保证像“int/* space not necessary around comments */main()”这样的表达式在替换之后仍然是合法的。
希望上面的这些简单的例子能够引起你使用 Vim 的正则表达式高效完成任务的兴趣。进一步的信息可参考“:help regexp”。
1.8. 自动完成和路径设定Vim 支持单词的自动完成。比如,你前面使用了一个很长的变量名,叫 aLongVariable,下面你在输入时,就不用完整键入了。很可能,你只需要键入“aL”,然后按下“Ctrl-P”(向前搜索可匹配的单词并完成)就可以得到完整的变量名(没有得到想要的结果的话,多按几下“Ctrl-P”;或者前面多输入几个字符,如“aLongV”)。类似的命令还有“Ctrl-N”(向后搜索可匹配的单词并完成)、“Ctrl-X Ctrl-L”(搜索可匹配的行并完成)、“Ctrl-X Ctrl-F”(搜索可匹配的文件名并完成)等,具体可参看“:help ins-completion”。
如果你并不熟悉这些功能,但也并不觉得这有什么稀奇的话,下面这个例子可能会让你觉得吃惊。请尝试打开一个空白的 C 文件(vim test.c),并输入:
1
2
3
4
| #include <stdio.h>
int main()
{
pri
|
最后一行不要回车,直接在“pri”后面输入“Ctrl-P”,你将看到“printf”出现。是的,虽然文件里没有“printf”,但 Vim 知道到哪里去寻找它!在作关键字匹配完成时,如果当前文件和其它打开的文件中没有想要的结果,Vim 会自动到“#include”的文件中进行进一步的搜索(为什么是“#include”呢?请查阅“:help 'include'”),搜寻的目录则由选项 path 决定,其缺省值在 Unix(含 Linux)下为“.,/usr/include,,”,代表搜索的目录依次是文件所在目录、/usr/include 和当前目录。根据实际情况,你可能需要在 .vimrc 文件中设置该选项,加入项目相关的包含目录,注意一般要保留最后的“,,”,除非你不需要在当前目录下搜索。
设置了合适的 path 后,另外带来的一个便利就是可以使用“gf”命令方便地跳转到光标下的文件名所代表的文件中。在上面的例子中,把光标移到“stdio.h”的任一字符上,键入“gf”,则 Vim 会自动打开 /usr/include/stdio.h 文件。使用“Ctrl-O”(参见“:help CTRL-O”)可返回到原先的文件中。
1.9. 文件跳转和 Tags大家一般都知道,在 Vim 的帮助窗口中的关键字上双击鼠标或者键入“Ctrl-]”即可跳转至该关键字相关的帮助主题。不过,“跳转至匹配的关键字”这一功能并不仅仅局限于帮助文件。只要有合适的 tags 文件(参见“:help tags-file-format”),我们同样可以在源代码中使用这个方便的功能,跳转到与关键字匹配的“标记”处(通常是源代码中某一函数、类型、变量或宏的定义位置)。
要产生 tags 文件,通常我们使用 Exuberant Ctags [15]。一般的 Linux 发布版中均带有这一工具。Ctags 带有的选项数量极多,此处我们仅简单介绍如何在一个典型的多文件、多层目录的项目中使用其基本功能:我们只需在项目的根目录处键入“ctags -R .”,Ctags 即可自动在文件中查找、识别支持的文件格式、生成 tags 文件。目前 Exuberant Ctags 支持多达 33 种编程语言 [16],包括了 Linux 下常用的 C、C++、Java、Perl、PHP 等。有了 tags 文件,以下的 Vim 命令就可以方便使用了(进一步的信息可参考“:help tags-and-searches”):
- :tag 关键字(跳转到与“关键字”匹配的标记处)
- :tselect [关键字](显示与“关键字”匹配的标记列表,输入数字跳转到指定的标记)
- :tjump [关键字](类似于“:tselect”,但当匹配项只有一个时直接跳转至标记处而不再显示列表)
- :tn(跳转到下一个匹配的标记处)
- :tp(跳转到上一个匹配的标记处)
- Ctrl-](跳转到与光标下的关键字匹配的标记处;除“关键字”直接从光标位置自动获得外,功能与“:tags”相同)
- g](与“Ctrl-]”功能类似,但使用的命令是“:tselect”)
- g Ctrl-](与“Ctrl-]”功能类似,但使用的命令是“:tjump”)
- Ctrl-T(跳转回上次使用以上命令跳转前的位置)
当我们在项目的根目录下工作时,上面这些命令工作得很好。但如果我们进到多层目录的里层再运行 Vim 打开文件时,这些命令的执行结果通常就变成了错误信息“E433: No tags file”。这是因为缺省 Vim 只在文件所在目录和当前目录下寻找 tags 文件,而我们前面只在项目的根目录下生成了 tags 文件,Vim 无法找到该文件。解决方法有好几种,我认为一般较简单的做法是对每个项目都在 .vimrc 文件中增加一个路径相关设定。假设我们有两个项目,位置分别是 /home/my/proj1 和 /home/my/proj2,那我们可以使用:
1
2
| au BufEnter /home/my/proj1/* setlocal tags+=/home/my/proj1/tags
au BufEnter /home/my/proj2/* setlocal tags+=/home/my/proj2/tags
|
Vim 选项 tags 用于控制检查的 tags 文件,缺省值为“./tags,tags”,即前面所说的文件所在目录下和当前目录下的 tags 文件。上面两行自动命令告诉 Vim,在打开项目目录下的文件时,tags 选项中的内容要增加项目的 tags 文件的路径。进一步信息可参看“:help 'tags'”。 |