Vi/Ex编辑器教程第一章Vi基础为什么选择VI一个贴心的编辑器。小何是个程序员,在一公司里与他人做共同维护的工作。刚从别人那里接手了一个大的模块。这个模块的代码真是满目疮痍啊,到处是修补的痕迹。而且看上去这些“意大利面条”式的代码补得不怎么牢靠;就在昨天这个模块彻底地崩溃了,使得这个部门几乎瘫痪。在一夜地奋战过后,小何终于在今早使这个模块又能运转了……在他打算出去买早餐时,该公司信息部门的副主管走过来了……“小何,这次的修复干得很好,辛苦你了。不过现在我需要这次崩溃的技术数据,要整理过的,马上。信息委员会的董事会早上召开了一个紧急会议,目的是评估问题是否在可控制的范围。如他们把矛头指向我,那我就倒大霉了。我需要有一些可以在投影机上播放的技术资料,以便转移他们的注意力。“他们很可能会让我讲一讲日志中导致这次崩溃的错误代码的相关记录……对了,这部分内容是记录在/oltp/err/m7中,日志是使用追加的方式因此最新的报告会记录在文件的底部。那些人对日志中旧的部分不感兴趣,他们认为那是历史了。另外除了市郊的火车时刻表外,他们不习惯看东西是从下往上的。所以你得重新整理一下顺序。“看一下,这是日志文件:374a1244872130295/074457nonabort5982d34971130295/221938nonabort853f72184140295/102309abort……“恩……向他们解释第二栏的数据等于跟他们说我们早知道这些缺陷的存在,只等着模块崩溃了──那是找死。你在编辑时记得,记得将第二栏中除首尾的两个数字外的其他数字删除。“对了,他们看那些看腻了后会想仔细地看一下Lint报告的。上个月我才跟他们说我们的代码无懈可击,现在我得说服他们相信这个模块现在还在不断输出的错误消息都是些无关紧要的小毛病引起的。你得对修补后的代码进行Lint检测然后把输出结果与源文件合并。方法是先在输出的结果中找这种的信息:Line257:obsoleteoperator+=然后把重要的部分放在源文件中相应行的末尾。中间用分隔符――如XXX分开,方便查找。没什么能比足量的源代码更能让会议提早结束了因为他们根本不知从何看起。“快去做吧。会议在35分钟后就开始了。”然后我们的副主管就走了。他是暗笑着走开的,因为他已经打好算盘了――他知道在这么短的时间里没人能做好他要求的那么多的编辑任务。这样等会他就不用费力的解释这次的崩溃了,他只需把责任推给他的下属。我就跟信息委员会的人说:“我已经跟程序员说过要在9:30之前做好报告了,而且讲很清楚了。但我刚问他时他说还没弄好而且不知何时会弄好。”然后:“这些程序员就是不能意识到时刻向管理层报告进度就跟程序中的每一个字节一样重要!”不过小何在与上级的角力中并非完全落于下风,他还有秘密武器:vi将文件中的行倒置对这个编辑器而言只是小菜一碟。以下的八个按键(在以下的文章中用(ret)表示按回车键)::g/^/m0(ret)就能完成这个工作了。将文件中所有行的第二栏首、尾以外的其他数字删除也只要一行命令::%s/^\([^]*[0-9]\)[0-9]*\([0-9]\)/\1\2(ret)那结合Lint报告与源代码呢?就算这种工作Vi也能自动做到自动化。这条命令::%s/Line\([0-9][0-9]*\):\(.*\)/\1s;$;XXX\2(ret)会把Lint的报告文件改为编辑器的脚本,只要在源文件中运行此编辑器脚本就能达到我们要的编辑目的了。小何只用了几分钟,输入了几行就避免了当冤大头。他现在还剩一些时间可以考虑怎样才能防止副主管推诿责任――他可以先到街对面的咖啡厅,等在会议开始的那时再再出现在会议厅中,并用在场每个人都听得到的方式告诉副主管:“你要的那些文件就在‘斜杠temp斜杠hal’文件夹中”。这篇教程的写作计划。我想写给那些对vi/ex有初步认识的编辑器用户。即你已经对一些类似“Vi入门”之类的书里教的那些普通的内容已经熟悉了。这种Vi的书籍在市场上泛滥却很少触及更深层次的东西。在这系列的教程中我们会深入的探索一些较不为人知的vi/ex的用途。其中有不多的技巧是通过一些我们经常使用的编辑功能来实现的,但我们确很少注意到这些技巧――举例来说,用global命令来对处理的每一行做记号。同时我还会对关于Vi的许多常见的误区进行阐述。要做到这些,我会很详细的解释里面的每个部分。我会在有必要的地方出些习题帮助理解。同时为了让你不至于被过多的模糊的信息所淹没,我会将这篇教程分成很多小块。然后用平稳合理的节奏将教程一篇篇地放到我们的网站上。关于这个编辑器的几个基本概念要真正理解这个编辑器的威力,你得对编辑器有一个基本的认识。它的许多功能便是筑在这些基础的概念上面。这个编辑器被误用的一个原因是许多用户包括有经验的用户,没有分清什么是它的本职而哪些工作不适宜用它来完成。这里有一个这个编辑器本职功能的参考列表:第一,编辑器严格地用来表示通用目的的编辑器。它不对文本进行格式处理;它不需要一个字处理器的支持;它不需要内建特殊的功能用来编辑十六进制、图形、表格、大纲或是支持任意一种编程语言──Lisp除外。它是二合一的编辑器。在可视模式(VisualMode)1[[1]注意这里的可视模式相当于Vim的一般模式和插入模式而不是Vim下的可视模式/圈选模式(也是VisualMode)]下,它是个比大多数的编辑器好的全屏编辑器,并且比那些同样支持一堆屏幕编辑命令的对手要运行得更快。它的行模式2(LineMode)[[2]行模式不是命令行模式的缩写。Ed/Ex类的编辑器中编辑是面向行的,用户使用命令对指定行进行编辑。这一类编辑器通常称为行编辑器。Vi的行模式即是使用Ex进行编辑的一种模式]使字处理器和简单的交互式编辑器的“全部查找和替换”功能相形见绌。它的仅有的对手是非交互式的编辑器,如Sed,使用这种编辑器你得事先准确的知道自己要做哪些编辑。但在vi/ex中,这两种工作方式被很发好地结合起来了。我用过的编辑器还没有哪一个会比vi/ex好――当把它的两方面的优势结合起来时。最后,这个编辑器只有抱着不怕苦不怕难的精神完整地学习过才能够用得得心应手。它的功能太多了,你根本没法在一或两个钟头里掌握。它又很特殊,用一个礼拜的时间都没法精通这个编辑器。但它的威力就在那儿,只有深入钻研的人才能掌握。这种威力很大一部分要靠对编辑器的个性化编程来实现:这不太容易,对熟练的用户而言它通过编程来扩展功能的能力要比任何的其他编辑器来得强或许(可能)只有Emacs例外。搜索式样在这个编辑器有有许多的功能,能通过使用字串式样的搜索来指定功能在哪里执行,范围有多大。这些搜索式样很能说明这个编辑器带有明显的Unix风格,但在搜索式样的细节上仍与其他的Unix工具有所区别。搜索式样功能在行模式下与可视编辑模式下大多数情况下有一样的使用方式,只有少数例外。但在输入搜索式样时怎样让编辑器根据不同的需要进行搜索呢?从当前位置开始搜索。通常我们用搜索式样是为了移动到文件中的另一个地方,或者把编辑命令的范围从当前位置扩展到式样所指明的位置。(在编辑模式下你还可以用一个式样的位置到另一个匹配式样位置作为执行动作的范围,但两个式样都是从当前位置开始搜索。)如果你想要从当前位置往下搜索(一直到文件尾),在搜索式样前面和后面加上一个斜杠(/)。因此如果你想要从当前位置开始查找文件中出现下一个字串“j++”的位置,输入:/j++/(ret)就行了。也可以这样:/j++(ret)注意,在式样与回车键(ret)之间没间隔,回车本身表示搜索式样的结束,所以第二个斜杠可省略。如果当前模式是可视模式,使用ESC键与回车键一样可以用来表示搜索式样输入完毕,所以在可视模式里如下命令/j++(esc)与之前的命令的作用是一样的。要向上搜索(回搜),在式样前后加上问号而不是斜杠。回搜与向下搜索使用同样的规则,因此?j++?(ret)?j++(ret)?j++(esc)都可用于回搜一样的字串。无论哪种方式你都只用了一个键来提交并确认搜索的式样和搜索的方向。可别以为回搜时在文件中找到的匹配项都是在当前位置以上,当前位置以下的匹配项也会被搜索到,反之亦然。编辑器在往回搜索时先在当前位置逐渐向上搜索,但当它到达文件的顶部(第一行)或底部(最后一行)时如果还没发现匹配项,它就会从文件的另一端开始往同一方向继续搜索。也就是当你用问号进行回搜时,编辑器会从当前行不断地往上找。如果到第一行还没找到匹配项,那它就会从最行一行开始继续搜,然后是倒数第二行,倒数第三行……依此类推,一直搜到最初开始搜索的位置(如果一直没找到匹配项的话)并停止搜索。或者当你往下搜索时一直到最后一行也没找到匹配项,编辑器就会从第一行开始继续搜索,然后第二行,第三行……如果你不想在搜索从文件的一端绕到另一端去继续,你需要一条行模式的命令::setnowrapscan(ret)这可以禁止当前会话3[[3]Vi可以同时打开多个文件,会话session就是当前编辑中的所有文件]中的绕回搜索。要在编辑时恢复绕回搜索功能,输入:setwrapscan(ret)就可以了,看你想开开关关几次都可以。“查找全部”搜索。目前为止,我只讨论了查找搜索式样的一个匹配项的方法――往指定的搜索方向查找在文件中距当前位置最近的一个匹配项。但搜索还有一种其他形式。这种形式的搜索主要用于行模式命令,如global和substitute。这种搜索方式找到文件中(或在文件中的指定部分)包含式样的所有行并对它们进行操作。在使用global和substitute时不要被它们搞晕了。在命令行中两种形式的搜索方式经常混合使用。但“查找一个匹配项”的式样一般置于命令名或命令缩写前而“查找全部”的式样则放在命令后。比如,在以下命令中::?Chapter10?,/TheEnd/substitute/cat/dog/g(ret)前两个式样分别用来匹配在当前行之前且距当前行最近的包含“Chapter10”字串的行和在当前行之后的第一个包含“TheEnd”字串的行。请注意每个地址(即包含式样的行的地址)只对应一行。以半角逗号隔开表示substitute命令作用于这两行和这两行之间的所有行。但紧跟着substitute命令的式样指示命令对命令作用范围内的所有字串“cat”替换成“dog”。除了字面上意义的不同外,两种形式的搜索也使用不同的分隔符用以标识式样的开始和(有些时候需要)结束。对“查找全部”式样而言不需要指示往前或往后搜索。因此式样的分隔符不仅限于问号和斜杠,还可以用半角的标点符号――几乎所有的标点都可以,因为编辑器自动识别命令名后的第一个标点符号为该命令中的分隔符。因此这几个替换命令:?Chapter10?,/TheEnd/substitute;cat;dog;g(ret):?Chapter10?,/TheEnd/substitute+cat+dog+g(ret):?Chapter10?,/TheEnd/substitute{cat{dog{g(ret)都是一样的。(最好不要使用在命令中有特殊意义的标点,如感叹号经常用作一个开关选项出现在命令名的后面。)使用其他标点作分隔符在搜索式样本身包含“斜杠”时就显示出了其优点。例如,当我们要将文本中所有的连续的两个“斜杠”用“-”隔开,即将“//”替换为“/-/”。很明显这样做::?Chapter10?,/TheEnd/substitute/////-//g(ret)是不行的。这个命令会将前三个斜杠视为分隔符,而对后面的所有字符视而不见。这个问题可以用“反斜杠”来解决::?Chapter10?,/TheEnd/substitute/\/\//\/-\//g(ret)但这个比上一个错误的命令还难正确地输入。当用其他的标点作为分隔符时:?Chapter10?,/TheEnd/substitute;//;/-/;g(ret)容易输入也更容易理解该命令的作用。简单搜索式样。最简单的搜索式样是直接输入要编辑器查找的字符。就如:“thecat”。但