Unix 之书
☰ 目录

第 22 章...

vi 文本编辑器 (The vi Text Editor)

文本编辑器(TEXT EDITOR,常简称为编辑器 EDITOR)是一种 用来创建和修改文本文件的程序。当你用这样的程序 修改一个文件时,我们就说你正在 编辑(EDIT)这个文件。

你必须学会熟练地使用 Unix 编辑器,这一点十分重要, 因为只要你想处理纯文本,就需要用它。如果你是 程序员,你会需要编辑器来编写程序。对于 非编程任务,你也需要编辑器来 创建和修改配置文件、Shell 脚本、 初始化文件、网页、简单文档等等; 事实上,处理任何包含文本的文件时你都需要编辑器。

与文字处理软件不同,编辑器只处理 纯文本,也就是由可打印字符组成的数据: 字母、数字、标点、空格和制表符(参见第 19 章)。 通常,编辑器只使用一种简单的等宽字体。因此,当你要准备的文档 包含多种字体,或者使用不同的 字号、颜色、斜体、粗体或其他属性时,不要 使用编辑器。 这类工作应当使用文字处理软件。

本章我们要讲 vi,Unix 编辑器中最重要的一位。虽然我无法 把每样东西都解释清楚 — 那至少要花 好几章的篇幅 — 但我会把你在大多数场合 需要知道的大部分知识讲给你听。(注: 名称 vi 按两个单独的字母发音: "vee-eye"。)

整章中我们都会谈论编辑"文件", 尽管严格说来,我还没有解释文件究竟是什么。我会 在第 23 章讨论 Unix 文件系统时 讲清楚这一点。目前,我们只需凭直觉理解: 文件有名字,也包含信息。 例如,你可以用 vi 编辑一个名为 essay 的文件,里面存着 你写的一篇文章的正文。

初学 vi 时,你会觉得别扭。 这一点无法回避。如果你感到 沮丧,请记住,每一个人都曾有过同样的感受。 然而,一旦你有了经验,一切就会说得通, vi 也会显得自然而易用。这正印证了 下面这条提示,在第 1 章里,我们曾把它 用于整个 Unix:

— 提示 —

vi 用起来简单,学起来难。

为什么 vi 如此重要?
(Why Is vi So Important?)

Unix/Linux 文本编辑器有很多种, 包括我们在 第 14 章讨论过的那些简单编辑器(keditgedit、Pico 和 Nano)。不过,两大主要 Unix 文本编辑器 是 vi 和 Emacs,两者都已问世 多年。它们都是功能强大、成熟、特性完备的程序, 也是迄今为止使用最广泛的 编辑器。它们中的任何一个都能满足你的需要。

vi 和 Emacs 彼此差异极大, 正如你可以想象的,人们在 "哪个更好"这个问题上常常意见不一。 事实上,Unix 社区已经为此争论 多年。真相是,vi 和 Emacs 代表着处理文本编辑这项工作的两种完全不同的 思路。因此,世上有 vi 派,也有 Emacs 派, 谈到编辑,他们看到的世界并不相同。 最终,当你足够有经验时, 你自己也不得不做出选择:你是要做 vi 派,还是 Emacs 派?

眼下你只需要明白:vi 和 Emacs 都 非常复杂,学起来都很费时间。本章我要教你 如何使用 vi,因为它是 两者中更重要的那个。以后如果你愿意,可以自学 Emacs。

无论你最终选定哪个程序作为 主力编辑器,你都必须 学会 vi。原因是,不像其他任何编辑器,vi 无处不在。使用它的人太多、时间太久,以致 你在世界上几乎每一个 Unix 和 Linux 系统上都会找到它。更正式地说,vi 是两大 Unix 规范的一部分:POSIX (参见第 11 章)和单一 Unix 规范 (即被称为"Unix"必须满足的标准)。 因此,按定义,每一个 Unix 系统都必须提供 vi,无论它多么冷门。 这意味着,一旦你学会如何使用 vi, 你就能在你可能遇到的任何 Unix 或 Linux 系统上编辑文本,也就是说,你随时都能 编辑配置文件、创建 初始化文件,或编写简单的 Shell 脚本。

当你处在工具有限的环境里工作时, 这一点尤其重要。例如, 如果你的系统出了问题,而你要从 救援磁盘引导,你很可能会发现 vi 是 唯一可用的文本编辑器。同样,vi 往往也是嵌入式系统上唯一可用的编辑器 (手机、DVD 播放器、家用电器等计算机化设备)。

就我个人而言,从 1976 年起我就一直在使用 Unix,从未见过哪一个系统没有 vi。(*) 实际上,vi 编辑器用得如此广 泛、如此重要,以致如果你去应聘 Unix 或 Linux 相关工作,人们会默认你懂 vi

* 脚注

有些 Linux 发行版不会自动安装 vi,也不会安装 Emacs。最常见的原因是 发行版的创作者不愿在永无休止的 vi/Emacs 之争中 偏向任何一方。作为替代,系统会默认安装 较简单的文本编辑器之一, 例如 Nano(参见第 14 章)。请放心,如果你遇到这样一台 机器,安装 vi 是很容易的。

vi 简史
(A Quick History of vi)

vi 编辑器由 Bill Joy 在 20 世纪 70 年代末创建,当时他还是加州大学伯克利分校的 研究生(见图 Figure 22-1)。Joy 是一位技艺 惊人的程序员,也是 Unix 早期最多产、最重要的贡献者之一(参见第 2 章)。除 vi 之外,Joy 还完成了最早的 BSD(Berkeley Unix)、C-Shell,以及第一套 稳健的 TCP/IP 实现,而 TCP/IP 正是支撑 互联网的协议。1982 年,他参与创办了 Sun Microsystems,在那里为 NFS (Network File System)和 SPARC 微处理器 架构的发展做出了重要贡献。要理解 70 年代末 Joy 作为研究生怎么会写出 vi,我们 有必要回到 Unix 的早期岁月。

Figure 22-1: Bill Joy 与 Dennis Ritchie

20 世纪 70 年代末,Bill Joy(右)是 加州大学伯克利分校的研究生。在那段 时间里,Joy 写出了 vi 编辑器和 C-Shell,并组织了最早的 BSD(Berkeley Unix)版本。这张照片摄于 那段时期,照片中 Joy 与 原始 Unix 操作系统的共同开发者 Dennis Ritchie(左)在一起(参见第 3 章)。Joy 左袖上(在你看来是右边)的圆徽章上写着 "The Joy of Unix"。

第一个重要的 Unix 编辑器是 ed (按两个单独的字母发音,"ee-dee")。它由 Ken Thompson 于 1971 年在贝尔实验室编写,Thompson 是 Unix 两位原始创造者之一(另一位是 Dennis Ritchie;参见第 1 章和第 2 章)。ed 编辑器是一个面向行的编辑器(LINE-ORIENTED EDITOR,或称 行编辑器 LINE EDITOR),意思是它按编号的文本行来工作。 例如,你可以输入一个命令来打印第 100 行 到第 150 行,或者删除第 17 行。之所以 必须采用这样的做法,是因为早期终端速度慢、功能有限 (第 3 章有相关介绍)。

1975 年秋,Bill Joy 离开密歇根大学,到 加州大学伯克利分校攻读计算机科学研究生。他原本 打算研究计算理论,那是一门相当数学化的学科。然而他的 工作把他引向了编程,并且机缘巧合之下 走向了 Unix。原因是 Ken Thompson 恰好 同时也在伯克利。

Thompson 正在贝尔实验室休学术年假, 他决定在伯克利——他的母校——做一年访问 教授。他到达时,伯克利计算机科学系刚刚 弄到一台崭新的 PDP 11/70 小型机。Thompson 带着两名学生,把最新版本的 Unix(Version 6)安装到新计算机上, 随后又安装了一套可在 Unix 下运行的 Pascal 系统。 (Pascal 是一种编程语言,由瑞士计算机科学家 Niklaus Wirth 于 1970 年创造,用于教授 结构化程序设计。)

Joy 和另一名学生 Charles Haley 对 Thompson 的 Pascal 实现很感兴趣,他们决定 用它来研究一般的上下文无关解析 算法(用于分析源程序结构的方法)。但 Joy 和 Haley 很快发现, 这套 Pascal 系统存在明显局限。 他们开始动手修补这些问题,结果一头撞上了 简陋的 ed 编辑器。

Joy 和 Haley 觉得 ed 实在太让人 抓狂,于是决定做一个更好的编辑器。当时, 另一位访问者、伦敦玛丽女王学院的 George Coulouris 也带来了 自己的软件:一个称为 em ("ee-em")的编辑器。Coulouris 把 em 做成 ed 的 向后兼容替代品。他选择 em 这个名字,意思是"editor for mortals"(编辑器给凡人用), 言下之意是 ed 不适合 普通人使用。Joy 和 Haley 把 em 的若干部分取出来放进 ed,做出一个混合体, 取名 en("ee-en")。

en 编辑器远不完善。Joy 和 Haley 都花了大量时间在上面,做出 一个又一个新版本。最终,他们 弄出了一个表现相当好的版本,取名为 ex("ee-ex")。与 ed 相比,ex 是一个改进极大的编辑器。不过 它仍是面向行的编辑器,这种程序 适合很老的终端和慢速调制解调器。

但那已是 1976 年,Joy 和 Haley 有了一台 新型终端可用:Lear Siegler ADM-3A。 ADM-3A 比 ed 当年在贝尔实验室 开发时所用的老式 Teletype ASR33 精密得多(参见第 3 章)。ASR33 把输出 一次一行地打印在纸上,而 ADM-3A 有一台显示器, 能够在屏幕上的任意位置显示文本。你可以在 图 Figure 22-2 中看到 Lear Siegler ADM-3A 的照片,并可把它与 下面图 Figure 22-3 中 Teletype ASR33 的照片 作个比较。

Figure 22-2: Lear Siegler ADM-3A 终端

人们熟知的一条规律是, 软件的增强往往由新硬件带动。 1976 年 Bill Joy 开发 vi 文本编辑器时正是如此。更早的 面向行的编辑器,例如 edex,是为配合 原始的面向行终端而设计的, 例如 Teletype ASR33(参见 下面图 Figure 22-3)。然而 Joy 有机会使用一种新的面向屏幕的 终端 Lear Siegler ADM-3A。这种 终端的先进功能启发 Joy 创造了 vi, 一个面向屏幕的编辑器。详见 正文。

Figure 22-3: Teletype ASR33 终端

左边的照片显示了 Unix 的两位 原始开发者,Ken Thompson (坐者)和 Dennis Ritchie(站者)。 Thompson 正在使用两台 Teletype ASR33 终端之一,它们 连接到 1970 年夏天运抵贝尔实验室的 那台新 PDP-11 计算机上。右边 是 Teletype ASR33 的特写。ASR33 — 原始的 Unix 终端 — 把输出一次一行地打印在纸上。 因此,最早的 Unix 文本编辑器 ed (以及后来的 ex)是面向 行的。1976 年,伯克利的 Bill Joy 有机会使用面向屏幕的 ADM-3A 终端(参见 上面图 Figure 22-2)。他用这台 ADM-3A 扩展了 ex>,创造出 面向屏幕的 vi 编辑器。 详见正文。

 

为了用上 ADM-3A 更强的功能, Joy 扩展了 ex,另外做出了一个 面向屏幕的界面,取名为 vi ("vee-eye")。新的 vi 编辑器支持所有 ex 命令,同时新增了大量 可以使用整块屏幕的命令。例如,与老的面向行 编辑器不同,vi 允许你在编辑文件时 从一处可视地跳到另一处。你还可以在任意位置插入、 修改或删除文本,而不必操心行号。就这样, vi 成了一个面向屏幕的编辑器 (SCREEN-ORIENTED EDITOR,或称屏幕编辑器 SCREEN EDITOR)。

直到今天,vi 编辑器仍然建立在 面向屏幕的命令与面向行的命令这两种混合体之上。 因此,学习 vi 时,你必须同时掌握两类不同的命令。 正如你可以想象的,这意味着把 vi 学好需要一段时间。不过,你会看到,能够 同时以两种不同方式编辑数据,使它成为一个 特别强大的工具。

有趣的是,viex 其实 是同一个程序。如果你按通常方式、用 vi 命令启动这个程序,看到的是面向屏幕的界面。 如果你用 ex 命令启动这个程序,得到的就是 较老的面向行的界面。

名称的由来

ed, ex, vi


在 Unix 早期,许多命令都被起了 简短的两个字母的名字。惯例是把这样的名字按两个单独的 字母来读。例如,ed 读作 "ee-dee";ex 读作 "ee-ex"; vi 读作 "vee-eye"。把 vi 按 一个音节读成 "vye" 是不对的。

ed 这个名字的含义很简单:它是 "editor"(编辑器)的缩写。

ex 这个名字就不那么直白了。许多人 认为它的意思是"extended editor"(扩展编辑器)。 从某种意义上说这没错,因为 ex 极大 扩展了 ed 的能力。虽然细节有点模糊, 这个名字实际上是 ed... en... ex 这一命名序列的 延续。ex 的共同作者 Charles Haley 这样向我 解释:"我记得有过一个 en。我不记得 有过 eo。我想我们是从 en 走到了 "e-某个字母",也就是 ex。" Bill Joy 在一次采访中也有类似的说法:"我不知道 是否有过 eoep,但最后是 ex。我记得 en,但不知道怎么 就到了 ex。"

无论如何,当 Joy 通过加入面向屏幕的命令 来扩展 ex 时,他选了一个新的两个字符的名字 vi,意思是"visual editor"(可视编辑器)。

这么多命令之所以取这样短的名字,有两个实际的 原因。第一,聪明人往往偏爱简短、易用的 缩写。第二, 老式终端慢得令人痛苦,使用简短、容易 打对的命令名更方便。

如果你想知道哪些两个字母的命令名今天仍在使用, 可以看看第 20 章的末尾,那里我演示了如何用 grep 程序在你自己的系统上找出这些名字。 如果你有空,不妨在联机手册里查一查 这些命令:你会发现一些被遗忘的珍宝。

Vim:vi 的替代方案
(Vim: an Alternative to vi)

vi 编辑器由 Bill Joy 于 1976 年创造,并在 1978 年年中作为 2BSD(Berkeley Unix 的第二个版本) 的一部分发行。最终,这款编辑器变得极为流行, 以致 AT&T 把它收进了 System V,使 vi 成为事实上的 Unix 编辑器。多年来,随着维护责任从 Joy 移交到其他程序员手中,许多人都为 vi 做过工作。使用与修改这个程序的人如此之多,你预计 它会被增强,而事实也确实如此。不过在 1992 年之前, 这些增强都相对微小。

这是因为 Joy 的原始设计实在出色, 以致很长时间里没有对重大改进的迫切需求。 确实,直到今天,当你使用 vi 时,你眼前看到的与 70 年代末 BSD 用户 使用的几乎一模一样。这并不是说 vi 无法 改进,只是这样的改动会根本到 把 vi 变成一个截然不同的 程序的程度。

事实上,这正是发生的事情。20 世纪 80 年代末,人们为非 Unix 系统创造了一个名为 STvi(常写作 STevie) 的开源 vi 克隆。 1988 年,荷兰程序员 Bram Moolenaar 拿走 STvi, 用它创造了一个新程序,取名 VIM,意思是"vi imitation" (vi 模仿)。几年来,Moolenaar 一直在改进 Vim,修复缺陷、加入 新功能,直到 1992 年发布了该程序的第一个 Unix 版本。此时增强功能已经多得 让 Moolenaar 改变了这个名字的含义。程序仍叫 Vim, 但 Moolenaar 宣布,从此以后这个名字代表 "vi improved"(改进的 vi)。

整个 90 年代,Vim 日益流行, 尤其是在 Linux 社区中更"极客"的那部分人中间: 程序员、系统管理员、 网络管理员等等。到 2000 年代初,Vim 已经流行到成为这类用户中大多数人的首选编辑器, 到 2005 年,许多 Linux 发行版 已用 Vim 取代了 vi。确实,如果你现在 正在使用 Linux,很可能你的系统里 只有 Vim 而没有 vi。若是如此,当你输入 vi 命令,或当你查看 vi 的 手册页(man vi)时,你得到的将是 Vim,而不是 vi。多数非 Linux 系统并非如此。 事实上,在许多种 Unix 上, 除非你自己安装,否则系统里很可能没有 Vim。

本章我们不会过多讨论 Vim, 而主要讲标准 vi:这个多年来一直是文本编辑 主力的经典程序。我之所以选择这样讲, 有两个原因。

第一,Vim 并不是 vi 的一个新版本, 甚至也不是 vi 的扩展。Vim 是一个完全不同的 程序,只是与 vi 向后兼容。 这个区别很重要。运行 Vim 时,你用的编辑器有许多 vi 中不存在的精密功能。 当然,由于 Vim 向后兼容,你可以 使用所有标准 vi 命令。然而,Vim 提供的 新功能涉及面如此之广,以致使用 Vim 的 体验与使用 vi 大不相同。

vi 对初学者来说已经够难,Vim 可能 更难,因为它要求你学会的不仅是所有 viex 命令, 还包括所有额外的 Vim 命令。而且,当你 使用 Vim 的特殊功能时,你解决每一个具体问题的 策略会与你使用 vi 时明显不同。 因此,如果你想学 Vim,最好的策略是 先学 vi

这听起来复杂,其实并不。因为 Vim 制作得 十分精巧,你完全可以把它当作 vi 来用。等到 后来,你对 vi 已经运用自如,就可以 开阔视野,自学如何充分 利用 Vim 的强大能力。为了帮你做到这一点, 本章末尾我会花一点时间介绍 Vim 的额外 功能,并演示如何入门。

我们之所以专注于 标准 vi 的第二个原因是:就是一项标准。 无论你使用哪种 Unix 或 Linux,无论你的系统多么 小巧,vi 都是唯一可能随处可用的 功能完整的文本编辑器。因此,即使你个人 首选的编辑器是 Vim、Emacs、Nano、Pico 或别的什么, 使用 vi 也是你必须掌握的一项基本技能。

— 提示 —

你怎么知道系统用的是 Vim 而不是 vi?输入显示 vi 手册页的命令:

man vi

如果你看到的是 Vim 的手册页,就说明你的系统用的是 Vim 而不是 vi

启动 vi
(Starting vi)

启动 vi 的基本语法是:

vi [-rR] [file...]

其中 file 是你要编辑的文件的名字。

vi 程序非常复杂,你可以想象, 它有许多选项。不过多数时候 你并不需要它们。确实,在正常情形下,你只需了解两个 选项:-r-R,本章后面 我们都会讲到。

要用 vi 编辑一个已存在的文件,只需指明 文件名,例如:

vi essay

要创建一个新文件,你有两种选择。第一,你可以 指定一个文件名。如果该文件不存在, vi 会替你创建它。例如,要用 vi 创建一个名为 message 的全新文件,你可以这样写:

vi message

另外,你可以只输入 vi 命令而不带文件名,以此创建一个空文件:

vi

这是告诉 vi 创建一个没有名字的新文件。 你可以稍后再指定名字,到保存数据的时候再说。

— 提示 —

如果你忘了自己正在编辑哪个文件(这比你想象的 更容易发生),按 ^G(*)。这会 显示文件名以及你在文件中的位置。

* 脚注

正如我们在第 7 章讨论过的,Unix 的惯例 是用 ^(抑字符)作为 <Ctrl> 的缩写。 因此,^G 指的是单个字符 <Ctrl-G>。

启动 Vim:vim
(Starting Vim: vim)

如我们前面所讨论的,在有些系统上,尤其是 Linux 系统上,vi 已被 Vim 取代。如果 你的系统正是如此,我的目标是让你把 Vim 当作 vi 来用。等你以后对 vi 运用自如了, 再自学如何使用 Vim 提供的扩展功能(本章结尾我们会再谈这件事)。

眼下你只需要知道如何让 Vim 表现得像 vi 那样启动。总的来说,启动 Vim 与启动 vi 完全一样。如果你指定一个已存在文件的名字,Vim 会为 你打开它。如果文件 不存在,Vim 会为你创建它。如果你 不指定文件名,Vim 会创建一个空文件,等你保存时 再取名。基本语法是:

vim -C [-rR] [file...]

-r-R 选项的作用与 vi 中相同: 本章后面会讲。眼下我唯一要说的选项是 -C。在 vi 和 Vim 内部,都有许多 内部设置可以改变程序的行为。当你用 -C 启动 Vim 时,它会更改这些设置,使 Vim 尽可能表现得与 vi 一致。当你这样使用 Vim 时, 我们说它运行在兼容模式 (COMPATIBILITY MODE(*))下。在你尚未精通 vi、 还没准备好切换到完整的 Vim 之前,最好始终 以兼容模式启动 Vim。例如:

vim -C essay
vim -C

* 脚注

在有些系统上,当你使用 -C 选项时 Vim 不会进入 兼容模式。如果你的系统是这样,你可以 通过在 Vim 初始化文件(本章后面会讲)中打开 compatible 选项,把 Vim 强制置于 兼容模式。

第一条命令告诉 Vim 你要处理一个名为 essay 的文件。如果文件存在,Vim 会为你 打开它;如果不存在,Vim 会创建它。 第二条命令告诉 Vim 创建一个全新的、未命名的 文件。当你想创建一个新文件但还没决定 给它取什么名字时,就用这条命令。

如果你的系统上 Vim 已经取代了 vi,那么 vi 命令的效果与 vim 命令相同。 在这样的系统上,下面两条命令 与前面的命令等价:

vi -C essay
vi -C

每当你启动 Vim 而不指定文件名时, 程序都会显示一些有用的信息(见 图 Figure 22-4)。这些信息只是为方 便你而显示。一旦你开始 输入数据,Vim 就会把它们去掉。

Figure 22-4: Vim 的启动屏幕

当你启动 Vim 而不指定文件 名时,你会看到一个带有有用信息的启动 屏幕。显示这些信息是为了方 便你:一旦你开始输入数据,它们就会消失。

               VIM - Vi IMproved

                 version 7.0.42
            by Bram Moolenaar et al.
       Modified by <bugzilla@redhat.com>
   Vim is open source and freely distributable

            Sponsor Vim development!
    type  :help sponsor<Enter>    for information

    type  :q<Enter>               to exit
    type  :help<Enter>  or  <F1>  for on-line help
    type  :help version7<Enter>   for version info
 
          Running in Vi compatible mode
    type  :set nocp<Enter>        for Vim defaults
    type  :help cp-default<Enter> for info on this

— 提示 —

下面这个简单的测试可以看出你的系统 用的是 Vim 还是 vi。输入 vi 命令,不带文件名:

vi

如果你看到的大体是空屏幕,你用的就是 标准 vi。如果你看到图 Figure 22-4 中显示的特殊帮助 信息,你用的就是 Vim。(测试完成后, 输入 :q 退出程序,即一个冒号加一个小写 "q"。)

— 提示 —

要把 Vim 当作 vi 来用,就用 -C 选项以兼容模式启动程序。为方 便起见,你可以用下面两条命令之一创建一个别名。第一条 适用于 Bourne Shell 家族(Bash、Korn Shell);第二 条适用于 C-Shell 家族(Tcsh、C-Shell):

alias vi="vim -C"
alias vi "vim -C"

要让这个别名永久生效,把命令写进你的 环境文件(参见第 14 章)。这样一做, 你就可以用 vi 命令以兼容模式运行 Vim, 用 vim 命令以 Vim 的本色模式运行。

命令模式与输入模式
(Command Mode and Input Mode)

当你用 vi 工作时,你的数据保存在一块 称为编辑缓冲区(EDITING BUFFER)的存储区里。当你告诉 vi 你要编辑某个文件时,vi 会把该文件的内容复制到编辑缓冲区, 所以你处理的是数据的副本,而不是 原件。理解编辑缓冲区对于使用 vi 至关 重要,继续阅读时请记住这个概念。

花一点时间想想使用文字处理软件是什么感觉。 你可以把光标移动到任何你想要的地方,然后直接开始 输入。当你需要在文件内从一处移动到 另一处时,你可以用鼠标,或者用键盘上的 专用导航键。在 PC 上,这些键是 <PageUp>、 <PageDown>、<Home>、<End> 以及 光标控制(箭头)键。当你需要使用某个 命令时,你从下拉菜单中选择一项, 或者使用某种特殊组合键。

1976 年 Bill Joy 开发 vi 时, 终端还没有导航键,也不支持带鼠标、 下拉菜单、功能键甚至 <Alt> 键的图形用户界面。 键盘上只有字母、数字、标点, 以及少数几个杂项键,例如 <Shift>、 <Ctrl>、<Return> 和 <Esc>。没有鼠标 也没有导航键,就没有简单的方式把光标从一处移到 另一处。没有下拉菜单或特殊按键,用户该怎样 指明命令(例如插入、修改、删除、复制或粘贴) 也就不那么显然。

Joy 选择的解决方案是把 vi 设计成在 两种不同模式下工作。在命令模式(COMMAND MODE)下, 你敲下的任何键都被解释为命令。例如, 在命令模式下,单个字母 x 是 删除一个字符的命令;组合键 dd 是删除整行的命令。这样的一字符和二字符命令 有很多,而要精通 vi,你必须 学会其中许多。这听起来很难,但只要 稍加练习,vi 的命令其实 相当好用。

第二种模式是输入模式(INPUT MODE)。在这种模式下, 你输入的一切都直接插入编辑 缓冲区。例如,在输入模式下,如果你输入 "Hello Harley",这 12 个字符就会被插入编辑缓冲区。 如果你按 x 键,插入的是 "x";如果你按 dd, 插入的是字母 "dd"。

这套系统的妙处在于它不需要任何特殊 东西,比如导航键或鼠标。 因此,你可以在任何类型的终端上使用 vi, 甚至通过远程连接使用。你唯一需要的特殊键是 <Ctrl> 和 <Esc>,而 70 年代末 所有通用终端上都有这两个键。

当然,要让这套系统工作,必须有办法 从命令模式切换到输入模式,再切回来。vi 启动时,你处于命令模式。要改变到输入模式, 你使用若干命令之一(它们你会 依次学到)。一旦进入输入模式,切回命令 模式很容易:只要按 <Esc>(Escape)键。 如果你已经在命令模式,却又按了 <Esc>, vi 会发出蜂鸣声。

如果你好奇为什么选 <Esc> 键来 做这件事,请看一下图 Figure 22-5: 一张摘自 ADM-3A 操作员手册的 ADM-3A 终端键盘布局图。 如我们前面所说,这正是 Bill Joy 开发 vi 时使用的终端。注意 <Esc> 键在键盘 左侧的位置,就在 <Ctrl> 键上方。这样一个重要的 键来说,这是个好位置, 因为左手第四或第五根手指很容易够到它。

Figure 22-5: ADM-3A 终端的键盘布局

一张摘自 ADM-3A 操作员手册的 Lear Siegler ADM-3A 终端键盘布局图。 Bill Joy 就是在这类终端上开发出 vi 文本 编辑器的。<Esc> 键位置顺手,这是 Joy 选择它作为 从输入模式切换到命令模式 的手段的原因之一。详见 正文。

现在,看看你自己的键盘。你会注意到 <Esc> 键已被移到一个方便程度大大降低的 位置:键盘最左上角。<Esc> 键原来的位置 正是你现在的 <Tab> 键所在,就在 <Caps Lock> 键上方。花点时间比较一下 按 <Tab> 键和按 <Esc> 键的感觉。Bill Joy 挑选 <Esc> 作为从输入模式切换到命令模式 的按键时,那个键很容易够到,这使得切换模式 快速而轻松。如今按 <Esc> 键需要长长地、别扭地伸一下手, 使切换模式变慢,也不那么舒服了。 生活就是这样。

刚开始,必须切换到一个特殊模式才能开始输入数据 会让人觉得奇怪。别担心。就 vi 而言,熟能生巧, 而且练习还会带来一种从容和 流畅,让你能按你打字的极限速度工作。如果你 会盲打,你会发现一旦记住了基本 命令,vi 非常好用,因为你可以双手 不离开键盘就做你想做的任何事。

为了让你对按模式工作有个感觉,设想 下面这个场景。(不必在意细节, 我们后面会讲。)你想在一个名为 schedule 的文件中间加入一些数据。要运行 vi,你 输入命令:

vi schedule

vi 启动时做三件事。首先,它把 schedule 的内容复制到编辑 缓冲区。接着,它把光标定位在缓冲区 第一行的开头。最后,它让你处于命令模式。

你先使用合适的命令把光标移到你想加入新数据的 位置。然后你输入一个切换到输入模式的命令, 开始打字。此时,你输入的每个字符都 直接插入编辑缓冲区。打完之后,你按 <Esc> 切回命令模式。然后你把编辑缓冲区的 内容存回原文件并退出程序。

— 提示 —

请注意,虽然改变模式的是 vi, 但人们通常说得就好像做出改变的是 你,用户本人。例如,我可能说"处于命令模式时, 你可以使用许多命令",或者"要往编辑 缓冲区添加文本,你必须先切换到输入模式"。

说到计算机,这种说法非常 常见。这是因为有灵性的人往往 愿意与自己的工具认同,超过他们愿意承认的程度。

知道自己处于哪种模式
(Knowing What Mode You Are In)

传统上,vi 不会做任何事来告诉 你正处于哪种模式。人们只是期望你自己 知道。我知道这听起来非常令人困惑,但 其实不是。一旦你有了一些经验, 你的头脑就会跟踪正在发生的事情, 时时刻刻你都知道自己处于哪种模式。

如果你真的迷失了方向,请记住:如果你在命令模式 下按 <Esc>,vi 会对你蜂鸣作响。 因此,如果你不确定自己处于哪种模式,连按两次 <Esc> 即可。这保证会让你留在命令模式, 并且至少响一声。(因为如果你处于输入模式, 第一次 <Esc> 会切换到命令模式,第二次 <Esc> 会响。如果你已经在命令模式,两次 <Esc> 都会响。)

不过你也许会问,vi 为什么不 做点什么来显示你处于哪种模式?实际上, 如果你设置一个名为 showmode 的内部选项, 某些 vi 版本会在这一点上帮你的忙。 (本章后面我们会讲 vi 的选项。)要用的命令是:

:set showmode

一旦设置这个选项,vi 会在屏幕 最后一行显示一条消息,指出当前模式。(实际的消息 在不同 vi 版本之间可能不同,但你 不会有什么理解困难。如果你决定 永远都想设置 showmode,可以把这个命令 放进你的 vi 初始化文件,这样每当你启动 vi 时该选项就会自动设置。(本章后面我们会讨论 初始化文件。)

如果你是 Vim 用户,甚至不必设置 选项。默认情况下,每当你进入输入模式,Vim 都会在屏幕左下角显示下面这条提醒:

-- INSERT --

看到可视提醒固然不错,但 事实是它并无必要。正如我前面提到过的,一旦你习惯了 vi, 在命令模式与输入模式之间切换有了些经验之后, 你总会知道自己处于哪种模式。 正因如此,大多数有经验的 vi 用户甚至懒得去设置 showmode 选项,即使它可用。他们并不真正需要它 — 稍加练习之后 — 你也不需要。

— 提示 —

无论你多么聪明,vi 都会让你 更聪明。

以只读编辑器方式启动 vi:viewvi -R
(Starting vi as a Read-Only Editor: view, vi -R)

有时你可能想用 vi 查看一个 不应被改动的要紧文件。这有两种做法。第一,你可以用 -R(只读)选项启动程序。 这是告诉 vi 你不打算把数据 保存回原文件。(这个选项在 vi 和 Vim 中都有效。) 第二,你可以用 view 命令启动程序。

vi -Rview 其实 没有区别。哪个容易记就用哪个。因此,下面两条命令 是等价的:

vi -R importantfile
view importantfile

两条命令都是启动 vi,以只读方式 使用一个名为 importantfile 的文件。这样使用 vi 可以保护你,避免不小心修改 重要数据。

你可能在想,为什么会有人想用 vi 去处理一个不能修改的文件?如果你只是想显示这个文件, 为什么不干脆用 less(第 21 章)? 答案是 vi 非常强大,单就显示文件而言, 许多人更喜欢用 vi 的命令而不是 less。等你精通了 vi, 你也会有同感, 尤其当你需要检查大型复杂的 文件时。

系统故障后恢复数据
(Recovering Data After a System Failure)

偶尔可能会发生这样的事:你正编辑文件编辑到一半, 系统宕了,或者你丢了连接。若是这样,vi 通常能让你恢复你的数据。

你会记得,当你使用 vi 时,正在编辑的 数据保存在编辑缓冲区里。vi 会时不时地把 编辑缓冲区的内容存进一个临时文件。通常, 你编辑结束后 vi 会删除这个文件。 然而,如果程序异常终止, 临时文件还会存在,你可以用它 恢复数据。

要恢复一个文件,用 -r(恢复)选项启动 vi:

vi -r

这会列出所有可用于恢复的文件。现在你可以 用 -r 后跟文件名重新启动 vi。例如:

vi -r test.c

这会恢复你的文件,多半能让你回到 系统宕掉时所在的位置。

注意:不要把 -r(恢复)选项 与 -R(只读)选项搞混。

— 提示 —

Vim 通过把你的编辑缓冲区保存在交换文件 (SWAP FILE)中,提供了一个出色的恢复功能,交换文件 存放在与你正在编辑的文件相同的目录里。 (我们在第 23 章和第 24 章会讨论目录。)每次你输入 200 个字符, 或者每当你四秒钟没有输入任何东西时, 交换文件都会被自动更新。

要恢复一个文件,你必须用 rm 命令(参见第 25 章)删除交换文件;Vim 不会替你代劳。

交换文件的名字由一个 .(点) 加上原文件的名字,再加上 .swp 组成。例如,如果你正在编辑 一个名为 test.c 的程序,交换文件就是 .test.c.swp。如果你不删除交换 文件,那么下次你编辑原文件时,Vim 会创建一个名字略有不同的新交换文件,例如 .test.c.swo

退出 vi
(Stopping vi)

停止 vi 有两种方式。多数 时候,你会想保存工作成果然后退出。 但如果你不小心把数据弄乱了,你可能 想不保存就退出,以保住文件的 原始内容。无论哪种情形,你都必须处于命令模式才能 输入退出命令。如果你处于输入模式,必须先按 <Esc> 切换到命令模式。

保存工作成果然后退出的命令是 ZZ。(一会儿我会解释这个名字。) 按住 <Shift> 键,连按两次 <Z>。 你不需要按 <Return>:

ZZ

不保存工作成果就退出的命令是 :q!。输入这条命令之后,你确实需要按 <Return>:

:q!<Return>

在使用 :q! 命令之前,至少 三思。一旦你不保存数据就退出, 就没有办法把它找回来了。

本章后面我会解释为什么第二条命令 以冒号开头,以及为什么你需要按 <Return>。请放心,这一切都是有道理的。 (ZZvi 命令;:q!ex 命令。)眼下我只提一点:在 Unix 中,!(bang) 字符有时用来表示你想要跳过 某种自动检查。就 :q! 而言,! 告诉 vi 不要检查 你是否保存了数据。

— 提示 —

如果你用的是 Vim,当你把 :q! 命令打错时 可能会卡在进退两难的地方。原因是, 在 Vim 中,输入 q 是开始录制 宏的信号。(本章后面我们会讲宏。) 如果你碰到这种情况,你会在窗口最后一行看到 "recording" 消息。别慌。要停止宏录制功能, 你只要输入 q(代表 quit)直到 消息消失。

名称的由来

ZZ


应该有个快捷的方式来保存工作并停止 vi, 这说得通,但为什么是 ZZ?

假设这个命令有个更简单的名字,例如 s(代表 "save")。那会很方便,但如果你 以为自己在命令模式,而实际上 处于输入模式,会发生什么?你会开始输入 数据,不知不觉间就打了个 "s" 而停止程序。

选择 ZZ 这个名字,是因为它虽然 容易输入,但你几乎不可能不小心把它打出来。

vi 如何使用屏幕
(How vi Uses the Screen)

到这里,我想花点时间谈几个与 vi 如何使用屏幕有关的小话题。屏幕的最后一行称为 命令行(COMMAND LINE)。vi 以两种不同的 方式使用这一行:显示消息,以及在 你输入某些命令时显示它们(见下一节)。 屏幕上所有其他行都用来显示数据。

如果你的编辑缓冲区只包含少量数据, 可能就没有足够的行填满整个 屏幕。例如,假设你的终端或窗口有 25 行。 最后一行是命令 行,剩下 24 行用于显示数据。再假设 编辑缓冲区里只有 10 行数据。如果 vi 把 14 个空行 显示为空白,那会很让人困惑。毕竟,你的数据里 本来就可能包含空行。

因此,vi 用一个 ~(波浪号)字符标记每一个 空行的开头。你可以在图 Figure 22-6 中看到一个 例子。当你往编辑缓冲区添加新行时,它们会 占据越来越多的屏幕,波浪号也就消失了。

Figure 22-6: vi 如何显示空行

屏幕的最后一行,也就是命令行, 被 vi 以两种方式使用:显示消息,以及 在某些命令正在输入时显示它们。 所有其他行都用于显示数据。

当编辑缓冲区中的数据不足以 填满所有行时,vi~(波浪号)字符 标记空行。在这个 例子里,你可以看到 17 个空行,每一行都由 一个波浪号标记。随着更多数据行被 插入编辑缓冲区,空行会被用上,波浪号也就消失了。

1.  This window has 25 lines.
2.  The bottom line is the command line.
3.  The other 24 lines are used to display data.
4.
5.
6.
7.  Below are 17 empty lines, marked with tildes.
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~
"harley" [New file]

大多数时候,你用 vi 编辑的数据 由纯文本组成:字符、字母、数字、 标点等等。不过,如果有需要, 你也可以把控制字符(参见第 7 章) 插入编辑缓冲区。为此,按 ^V, 紧接着按你想输入的那个控制字符。 例如,如果你想输入一个真正的 ^C 字符,按 ^V^C。如果你确实想输入一个 ^V,就按 ^V^V

vi 显示控制字符时,你会看到一个 ^ 字符后面跟一个字母,例如 ^C。请记住, 你看到的是单个字符,尽管它在 屏幕上占了两格。

如我在第 18 章所解释的,制表符是 ^Ivi 编辑器和 Unix 一样, 假定制表位每隔 8 个位置设置一次。(你可以 改变这个设置,但大多数人懒得去改。)如果你在编辑缓冲区中插入一个制表符, 你不会看到 ^I。相反,vi 会显示 必要数量的空格,使你的数据看起来像是按制表符 对齐的。这只是为方 便你:这些多出来的空格并不真的存在。 实际上,每个制表符只是一个单独的字符 (^I)。

最后,如果任何时候你的屏幕弄乱了 — 比如你在远程工作而线路上有 噪声 — 你可以按 ^L,告诉 vi 重新显示 屏幕上的所有行。

使用 viex 命令
(Using vi and ex Commands)

我前面解释过,viex 其实 是同一个程序的两副面孔。这意味着当你使用 vi 时, viex 命令你都可以用。

多数 vi 命令是一两个字母。 例如,要把光标向前移动一个词,你用 w 命令(在命令模式下只要输入 "w")。 要删除当前行,你用 dd 命令(只要输入 "dd")。由于 vi 命令 如此短,你输入时不会回显。

多数 vi 命令你都不需要按 <Return>。例如,你一输入 "w", 光标就向前移动一个词。你一输入 "dd",当前行就消失了。如果你搞错了,输入了一个无效的 vi 命令,你会听到 一声蜂鸣。但不会有错误消息。 (那又有什么意义呢?)

ex 命令比 vi 命令更长也更复杂。 因此,你输入时它们会回显在命令行上。所有 ex 命令都以 :(冒号)开头。例如,下面这条命令删除第 1 行到第 5 行:

:1,5d

下一条命令把所有出现的 "harley" 改成 "Harley"。(暂不必在意细节。)

:%s/harley/Harley/g

你一输入开头的冒号,vi 就把光标 移到命令行(屏幕的最后一行)。你输入命令时, 每个字符都会回显。输入完命令后,你必须 按 <Return>。

如果你在你按 <Return> 之前发现输入有误, 你有两种选择。第一,你可以按 <Esc> 彻底取消这条命令。或者你可以用 图 Figure 22-7 中列出的特殊键来纠正命令(细节参见第 7 章)。你也可以在输入模式下用同样这几个键 在输入时做出更正。

Figure 22-7: 使用 vi 时用于更正的按键

当你用 vi 编辑器输入时,有三个 标准 Unix 键可以用来做 更正。细节参见第 7 章。

按键 用途
<Backspace>/<Delete>删除最后输入的字符
^W删除最后输入的词
^X/^U删除整行

在有些系统上,当你做出更正时,光标 会向后移动,但字符不会从屏幕上 被擦掉。例如,假设你输入:

:1,5del

在你按 <Return> 之前,你意识到命令 结尾不必输入 "el"。于是你按两次 <Backspace>。光标后退两格,但你仍然看到那两个 字符。别管它:它们已经没了。 只要按 <Return> 就行。

学习 vi 命令的策略
(A Strategy for Learning vi Commands)

vi 编辑器有大量形形色色的命令。 为方便起见,我们可以把它们分组如下:

• 移动光标的命令
• 进入输入模式的命令
• 做出修改的命令

我的目标是让你从每一组中学到足够的命令, 使你能够针对每一个具体时刻形成策略, 完成你所需要的任何编辑任务。 举个例子来说明我的意思。当你工作时,光标显示你在编辑缓冲区中的 当前位置。要把新数据插入缓冲区,你可以 采用下面这个策略:

1. 确认你处于命令模式。
2. 把光标移动到你想要插入数据的位置。
3. 切换到输入模式。
4. 输入数据。
5. 按 <Esc> 切换回命令模式。

一旦你学会了基本的 vi 命令,你会发现实现任何特定策略都有 许多不同的方式。你如何选择取决于 具体情形和你的熟练程度。

可能让你吃惊的一件事,是 vi 提供的命令数量之大。例如,仅仅进入输入模式就 有 12 种不同的命令;在命令模式下,仅仅移动 光标就有 40 种不同的命令(而这还只是简单的 光标命令)。

你可以猜到,没有人需要知道 12 种进入输入模式 的方法或 40 种移动光标的方法。 不过,我确实希望你能学会尽可能多的 vi 命令,因为信不信由你,这正是 vi 如此好用的原因。

例如,假设你想从屏幕左上角移动到屏幕向下约一半、向右几个词的位置。你可以一次移动一格, 那样又慢又别扭。但如果你知道全部 40 个光标移动 命令,你就能为当时的情形挑出最合适的那些, 只按三个或四个键,就能立刻把光标移动到你想要的 确切位置。

本章我会覆盖所有基本的 viex 命令。想要更全面的参考,你可以使用附录 C, 它汇总了所有重要命令。我的建议是不断自学, 直到你掌握附录 C 中的所有命令。时不时地花点时间 学一个新命令,然后练习它。所有命令都是有用的, 都值得练习。阅读本章余下内容时,我希望你 坐在计算机前跟着一起做。我们每讨论一个新命令, 我都希望你能花点时间动手试试。

最后,在书临近结尾处,你会发现一个 专门的 vi 索引,只属于本章。当你想查找 与 vi 有关的话题时,就该来这儿找。 (现在就花点时间找到这个索引。)

— 提示 —

vi 用得精妙的艺术,就在于能够挑选 最好的命令,以最简单、最快速的 方式完成一项任务。

创建练习文件
(Creating a Practice File)

阅读本章时,你需要一个文本文件来 练习编辑。你可以用下面两条命令中的任意一条创建它:

cp /etc/passwd temp
man vi > temp

第一条命令通过复制系统口令文件 创建一个较小的文件。第二条命令通过复制 vi 的手册页 创建一个较大的文件。(cp 命令在第 25 章解释;口令文件 在第 11 章,联机手册在 第 9 章,而用 > 重定向标准输出 在第 15 章。)

这两条命令都会留下一个名为 temp 的文件,你可以拿它来练习。一旦有了这样一个文件, 你就可以输入下面的命令来编辑它:

vi temp

练习结束后,你可以用下面的命令移除(删除)这个文件:

rm temp

(rm 命令在第 25 章解释。)

移动光标
(Moving the Cursor)

任何时候,光标都在你屏幕上的某一行上。 这一行称为当前行(CURRENT LINE)。在当前行内, 光标位于某个特定字符之上或之下,这个字符 称为当前字符(CURRENT CHARACTER)。 许多 vi 命令对当前行或当前字符 执行某个动作。例如,x 命令删除当前 字符;dd 命令删除当前 行。

每当你移动光标,新位置就成为 当前字符。类似地,如果你把光标移到新的一行, 那一行就成为当前行。每当你把光标移到 当前不在屏幕上的某一行时,vi 会显示 编辑缓冲区的另一部分,使新的 当前行可见。换句话说,要从编辑缓冲区的 一部分跳到另一部分,你只要移动光标即可。

vi 中,有许多不同的命令 可以移动光标,也就是说,在编辑缓冲区内从一处跳到 另一处有许多不同的方式。我的目标是教你 其中大部分命令。这样,无论何时你需要跳到编辑缓冲区的 另一处,你都能 判断出哪个命令序列最有效。用不了多久,挑选最快的光标 移动命令就会成为你的第二天性。

有些情形下,完全相同的光标移动有好几种做法。 例如,把光标向左移动一格就有三个 不同的命令。这种情况下,你不必把所有等价 命令都学会。挑一个你最喜欢的 并加以练习即可。现在,我们开始。

要把光标移动一格,你有很多 选择。最好用的命令是 hjkl。它们的用法如下:

h 光标向左移动一格
j 光标向下移动一格
k 光标向上移动一格
l 光标向右移动一格

为什么选了这样古怪的几个键?有两个原因。 第一,如果你会盲打,这四个键正好在 右手手指的基本键位附近,移动光标 极为方便。(看看你的键盘。)

第二,正如本章前面讨论过的, vi 是 1976 年由 Bill Joy 在一台 ADM-3A 终端上开发的。请看图 Figure 22-8, 那是一张 ADM-3A 键盘的特写照片,显示了这四个键。 注意字母上方的箭头。ADM-3A 在设计时就是把这四个 键用作光标控制键的,所以 Joy 以同样的方式使用它们也就顺理成章。

Figure 22-8: ADM-3A 终端上的 H、J、K、L 键

1976 年 Bill Joy 开发 vi 时,他用的 是一台 Lear Siegler ADM-3A 终端。在这种 终端上,H、J、K、L 这几个键 — 不仅用来打字 — 还用来控制 光标(注意箭头)。因此,Joy 用同样这四个键在 vi 中移动光标。 详见正文。

如果你会盲打,用 H、J、K、L 来移动 光标尤其方便。不过,也有其他更好记的键 可用。如果你的键盘上有光标控制键(箭头键), 你可以用它们。(我将把它们称为 <Left>、 <Down>、<Up> 和 <Right>。)你还可以 用 <Backspace> 向左移动,用 <Space> 空格键向右移动。

<Left> 光标向左移动一格
<Down> 光标向下移动一格
<Up> 光标向上移动一格
<Right> 光标向右移动一格
<Backspace> 光标向左移动一格
<Space> 光标向右移动一格

上下移动的另一种方式是用 - (减号)和 +(加号)命令。按 - 移到上一行的开头; 按 + 移到下一行的 开头。作为替代,按 <Return> 也会 移到下一行的开头。

- 光标移到上一行的开头
+ 光标移到下一行的开头
<Return> 光标移到下一行的开头

在当前行内,0(数字零) 命令移到行首; $(美元符号)命令移到行 尾。如果当前行有缩进,你可以用 ^(抑字符)移到本行中第一个 不是空格或制表符的字符。

0 光标移到当前行的开头
$ 光标移到当前行的结尾
^ 光标移到当前行中第一个非空格/制表符

除了按字符或按行移动光标, 还有几个命令可以让你从一个词跳到另一个词。 (在 vi 中,词(WORD)是由字母、数字或 下划线字符组成的字符串。)要向前移动,用 we 命令。w 命令移到下一个词的 第一个字符;e 移到当前词的最后一个字符(词尾)。 要向后移动,用 b 移到上一个词的第一个字符。你会 发现,用 web 常常是快速把光标送到你确切想要之处的办法,能省下 很多次按键。

w 光标向前移到下一个词的第一个字符
e 光标向前移到当前词的最后一个字符
b 光标向后移到当前词的第一个字符

这三个命令在每个标点字符处都会停下, 如果你的数据中没有太多这类字符,这没什么问题。 然而,如果你的数据里有大量 标点,这样移动就必然很慢。 此时你可以改用 WEB 命令。它们的用法相同,只是 只把空格和换行视为词的结尾。

W w 相同;忽略标点
E e 相同;忽略标点
B b 相同;忽略标点

例如,假设光标位于 下面这一行的开头:

This is an (important) test; don't forget to study.

如果你按几次 w,你会在每个括号、分号、 撇号(单引号)以及每个词的开头都停下来。 也就是说,你要按 w 13 次才能到达 这一行的最后一个词。如果你用 W,你 只会在每个空格之后停下。你只要 按 W 8 次就能到达本行的最后一个词。 (此刻正是你自己动手试试这些命令的好时机。)

更大范围的移动,可以用括号命令 在句子之间跳转:

) 向前移到下一个句子
( 向后移到上一个句子

类似地,花括号命令可以在 段落之间跳转:

} 向前移到下一个段落
{ 向后移到上一个段落

同样,这些命令你应当亲自试一试, 以确保你确切理解它们如何工作。 试的时候,请注意能够按词、句子和段落 四处跳转是多么顺手。然后花点时间想想 下面这个问题:英文文章是由词、 句子和段落组成的。为什么除了 vi, 几乎所有其他文本编辑器、没有任何文字处理软件 允许你直接对词、句子和段落 进行操作?

vi 内部,句子(SENTENCE)的正式定义是 一串以句号、逗号、问号或感叹号结尾, 后面跟着至少两个空格或一个换行符的字符。 (换行符标记一行的结束;参见第 7 章。)

换句话说,要让 vi 认出一个句子, 它后面必须跟两个空格,或者位于行尾。之所以 有这个要求,是因为使用两个空格可以让 vi 把 句子与词(词之间用单个空格分隔)区分开来。 看下面这个例子,它由两个句子组成, 用句号和两个空格分开:

Meet me at the Shell Tower at 6pm. Is this okay with you?

段落(PARAGRAPH)的定义是以空行开头和结尾的 一段文本。换句话说,在行首加一个 制表符还不足以表示一个新段落。

— 提示 —

一般说来,你最好养成在句子之间打两个空格、 在段落之间留一个空行的习惯。这样会起到三个作用:

第一,你的文章更容易读。

第二,当你使用 vi 时,从一个句子移到 另一个句子会很容易(用 () ),在段落之间移动也一样(用 {})。

第三,如果你把文本发给某个可能需要编辑它的人, 那个人会觉得你的文本好办得多。例如,假设你给别人 发了一封邮件,对方礼貌地在回信时编辑了你的文本。 如果原始邮件格式规范,他要删除整个 句子或段落就容易得多。这看上去也许是个小问题, 其实并不是。

预言:一旦你对 vi 运用自如, 你会发现自己会对那些写纯文本时句子之间只用一个 空格的人生气。

有时你会想在屏幕上从一处大跨度地 移到另一处。要开始这样的移动, 你可以用 HML 命令。它们分别跳到屏幕的顶部、中部 或底部("high"、"middle"、"low")。

H 光标移到屏幕第一行
M 光标移到屏幕中间一行
L 光标移到屏幕最后一行

一般说来,移动光标的诀窍在于用最少的 按键到达你想去的地方。 举个例子。假设你的光标在屏幕 第一行。屏幕最后一行数据 包含:

today if you can. Otherwise give me a call.

你想移到 "call" 中的 "c" 那里,以便插入 "phone" 一词。

你可以按很多次 <Down> 移到你想要的那一行, 然后再按很多次 <Right> 移到你想要的那个词。 不过,你可以用三次按键就完成 整件事:

L$b

1. 光标移到屏幕最后一行(L)。
2. 光标移到该行末尾($)。
3. 光标移到上一个词的开始(b)。

要增强光标移动命令的能力, 你可以在命令前输入一个数字来重复这次移动,这个数字 称为重复计数(REPEAT COUNT)。例如,要向前 移动 10 个词,输入:

10w

注意数字之后不加空格。这里 还有两个例子。要向下移动 50 行,输入 下面任意一条命令:

50j 50<Down> 50+ 50<Return>

要向后移动三个段落,给 {(左花括号)命令 配上重复计数:

3{

一般规则是,只要讲得通,任何 vi 命令前都可以加重复计数。

— 提示 —

每当你需要把光标从一处移到另一处时, 跟自己较个劲,看能不能用尽可能少的 按键完成(*)。

* 脚注

这也许能帮你赚够大学学费。抱着你的 笔记本电脑在学生活动中心转悠, 跟人打赌,说你把 vi 的光标从一点移到 另一点比谁都快。一开始 多用 <Up> 和 <Down> 这类 短距离移动命令。等你输了几次赌局、赔率升高之后, 你可以靠 HML,再加上带重复计数的句子和词 命令大赚一笔。

在编辑缓冲区中移动
(Moving Through the Editing Buffer)

任何时候,vi 都会显示编辑缓冲区中 能装进屏幕的那部分。当你处理 大量文本时,一次只能看到其中 一部分。例如,如果你的终端或窗口有 25 行,vi 一次 只能显示 24 行文本。(记住, vi 不在屏幕最后一行显示文本,那一行用作命令行。)当你想看文本的另一部分时,就需要 把光标移到编辑缓冲区中那个 位置。有几个命令可以 使用。

第一,你可以用 ^F(forward,向前)命令 向下翻一屏。(记住,^F 指的是 <Ctrl-F>。)相反的命令是 ^B (backward,向后),向上翻一屏。还有 两个变体:^D 向下翻半屏,^U 向上翻半屏。 当你想快速浏览文件时用 ^F^B; 当你想做较小的跳跃时用 ^D^U

^F 向下(向前)翻一屏
^B 向上(向后)翻一屏
^D 向下翻半屏
^U 向上翻半屏

一般规则是,如果你在光标移动命令前输入一个 数字,这个数字就充当重复 计数。例如,要一次向下翻 6 屏,输入:

6^F

要向上翻 10 屏,用:

10^B

既然你可以用 ^F^B 以这种方式跳 很长的距离,你就没有必要给 ^D^U 命令配重复计数。因此,当你在 ^D^U 前输入数字时,这个数字的含义 有所不同:它设置这两个命令各自应当跳过的 行数。例如,考虑下面 几条命令:

10^D
10^U

这两条命令中的任何一条都告诉 vi 跳 10 行,并且此后所有的 ^D^U 命令也都跳 10 行(直到你重新设置这个计数)。 如果你愿意,可以把行数设成一个 很大的值。例如,如果你想一次跳 100 行,用下面任意一条:

100^D
100^U

在你改变这个数字之前,所有 ^D^U 命令都会跳 100 行。

跳回先前的位置
(Jumping to a Previous Location)

很多时候你会把光标移得很远,过了一会儿 又想把它移回来。这样的移动有时是 有意的。比如, 你可能跳到编辑缓冲区末尾,加了一 行,然后想回到刚才正在做的地方。这种情况也可能 偶然发生:你出了错,突然发现自己 离刚才工作的地方很远。

在这些情形下,你可以用 `` 命令回到 先前的位置(也就是连续输入两个反引号)。要测试这个命令,用一个大文件启动 vi。然后给 G 命令配上重复计数,跳到第 10 行:

10G

现在用 l(小写 "l")命令移到该行的 第 8 个字符:

8l

接着,用 G 命令跳到编辑缓冲区的 末尾,输入:

G

要回到你先前的位置(第 10 行的第 8 个字符), 连续输入两个反引号:

``

这个命令的一个变体是用 ''(两个 单引号)代替两个反引号。这会跳到 行首,而不是行内某个位置。要测试这一点,再次把光标移到 第 10 行的第 8 个位置,然后跳到文件末尾:

10G
8l
G

现在,连续输入两个单引号:

''

注意光标在该行的开头。

这个命令更强的形式允许你用 一个看不见的名字标记任意一行。之后你可以随时用 那个名字跳到这一行。要 以这种方式标记一行,输入 m,后面跟一个 字母。这个字母就成为那一行的名字。例如,要把当前行 标记为名字 "a",输入:

ma

要跳到某个标记过的行,输入一个 `(反引号) 或 '(单引号),后面跟上行的 名字,例如:

`a
'a

第一个命令(反引号)跳到被标记行中 确切的位置。第二个命令 (单引号)跳到被标记行的行首。

— 提示 —

标记一行时,你可以用任何你想要的字母。 原则上,这允许你最多标记 26 行( az)。实践中,你 很少需要一次标记两行以上。 标记一行的最简单方式是输入 mm。之后你可以输入 'm 跳到这一行。(如果你要用两个标记, 就用 mamz。)

一旦你习惯了输入这些组合,标记一行、移动 光标去做别的事、然后跳回 原来那一行,这一切都会变成你的第二天性, 双手完全不必离开键盘。 (花点时间体会一下 vi 的强大。)

搜索模式
(Searching for a Pattern)

在编辑缓冲区中四处移动的另一种方法,是跳到 包含特定模式的那一行。为此, 你使用 /(斜杠)和 ?(问号) 命令。

你一按 </> 键,vi 就会在 命令行上(屏幕底部)显示一个 / 字符。 现在你可以输入任何你想要的模式并按 <Return>。 这是告诉 vi 查找该模式的下一个 出现位置。如果你想再次查找同一个 模式,只要再输入 / 并按 <Return>。

举个例子。你正在编辑一份你想给他们 寄钱的人的名单,你想找到模式 "Harley" 的下一次出现。输入:

/Harley

现在按 <Return>。光标会跳到包含该模式的 下一行。要重复这次搜索再跳一次, 单独输入一个斜杠,后面跟 <Return>:

/

由于你没有指明新的模式,vi 假定你想沿用上一个 / 命令用的那个。

默认情况下,vi 的搜索区分大小写。 因此,下面两条命令并不相同:

/Harley
/harley

vi 查找某个模式时,它从光标所在位置 开始向前搜索。如果到了编辑缓冲区的末尾,vi 会绕回 开头。这样,vi 就能搜索 整个编辑缓冲区,与你的起始 位置无关。

要向后搜索,你用 ? 命令。 例如:

?Harley

它的用法与 / 相同,只是 vi 向后搜索。一旦你用 ? 指定了一个 模式,你就可以单独用 ? 再次向后搜索同一个模式:

?

如果 vi 到了编辑缓冲区的 开头,它会绕到末尾继续向后 搜索。这样,vi 就能向后搜索 整个编辑缓冲区。

一旦你用 /? 指定了某个模式,还有两种方便的方式 继续搜索同一个模式。n(next) 命令沿原命令相同的方向搜索。N(大写 "N")命令 沿相反方向搜索。例如,假设你 刚刚输入了命令:

/Harley

现在你想找到同一个模式的下一次 出现。你只要按 n。(不要 按 <Return>。)这与你输入 /<Return> 而不带模式 是一样的。要反复搜索同一个模式, 想按几次 n 就按几次。如果你按 N, vi 会向后重复同一次搜索。与其他搜索命令一样, 必要时 nN 会绕过编辑 缓冲区的末尾(或开头)。

nN 的确切含义取决于 你最初的搜索方向。例如,假设你 输入了一条向后搜索的命令:

?Harley

n 会向后重复这次搜索(同一方向)。 按 N 会向前重复这次 搜索(相反方向)。

— 提示 —

虽然 /? 搜索命令是为 vi 开发的,但你会发现其他程序也在用它们。 例如,在用 less 显示文件时,你可以使用 一模一样的命令(参见第 21 章)。

为了更灵活,你可以用正则表达式 (regex)来指定模式。(正则表达式 在第 20 章有详细讨论;那里有大量例子可看。) 作为参考, 图 Figure 22-9 列出了在正则表达式中具有 特殊含义的各种元字符。

Figure 22-9: 用 vi 搜索时使用正则表达式

当你用 /? 命令在 vi 中 搜索时,你可以用正则表达式作为搜索模式来 增强搜索的能力。 这里列出的是这类表达式中最有用的 元字符,以供参考。细节 参见第 20 章。

元字符 含义
 .匹配除 newline 以外的任意单个字符
 ^锚定位:匹配一行的开头
 $锚定位:匹配一行的结尾
 \<锚定位:匹配一个词的开头
 \>锚定位:匹配一个词的结尾
 [list]字符类:匹配 list 中的任意字符
 [^list]字符类:匹配不在 list 中的任意字符
 \引号:把元字符按字面解释

下面几个例子展示了使用 正则表达式的威力。要搜索下一次出现的、"H" 后跟任意两个字符,用:

/H..

要搜索一个 "H" 后跟任意两个小写 字符,用:

/H[a-z][a-z]

要搜索一个 "H",后跟零个或多个 小写字符,再跟一个 "y",用:

/H[a-z]*y

要搜索下一行以 "Harley" 开头的行,用:

/^Harley

归纳一下:

/regex 向前搜索指定的正则表达式
/ 向前重复上一次的正则表达式搜索
?regex 向后搜索指定的正则表达式
? 向后重复上一次的正则表达式搜索
n 重复上一个 /? 命令,方向相同
N 重复上一个 /? 命令,方向相反

使用行号
(Using Line Numbers)

在内部,vi 通过给编辑缓冲区中的每一行 分配一个行号来跟踪它。如果你想 看到这些数字,就把 number 选项打开。(本章后面我们会讲 vi 的选项。) 要用的命令是:

:set number

例如,假设你正在用 vi 撰写 你的《应用哲学》学位论文。编辑 缓冲区里的内容是:

I have a little shadow that goes
in and out with me,
And what can be the use of him
is more than I can see.

如果你输入命令 :set number,你会 看到:

1  I have a little shadow that goes
2  in and out with me,
3  And what can be the use of him
4  is more than I can see.

必须明白的是,这些数字并不 真是你数据的一部分。它们只是为方 便你而显示。如果你想去掉这些数字, 可以按如下方式关闭 number 选项:

:set nonumber

如果行号是关闭的,你可以按 ^G 来查看 自己在文件中的什么位置。这会显示 文件名以及你在文件中的位置。

行号有两个重要用途。第一, 如你后面会看到的,你可以把它们与许多 ex 命令一起使用。第二,你可以用 G(go to)命令跳到指定的行。 只要输入行号,后面跟上 G。不要输入空格,也不要按 <Return>。 例如,要跳到第 100 行,输入:

100G

要跳到编辑缓冲区的开头,输入 1G:1。在新版本的 vi 中,你可以 用 gg 作为 1G 的同义命令。

另外,你也可以通过输入 :(冒号)、 后面跟行号、再跟 <Return> 来跳到指定的行。这里举几个例子。第一条 命令跳到第 1 行;第二条命令跳到 第 100 行;最后一条命令跳到文件 末尾。(指定行号时,$ 字符代表文件中的最后一行。)

:1
:100
:$

下面把所有变体汇总一下:

nG 跳到第 n
1G 跳到编辑缓冲区的第一行
gg 跳到编辑缓冲区的第一行
G 跳到编辑缓冲区的最后一行
:n 跳到第 n
:1 跳到编辑缓冲区的第一行
:$ 跳到编辑缓冲区的最后一行

你会发现 G1G(或 gg) 特别有用,所以现在花点时间把它们 记住。

插入文本
(Inserting Text)

正如本章前面讨论过的,要往编辑缓冲区插入文本, 你必须输入一个切换到输入模式的命令。当你 插入完文本后,按 <Esc> 离开 输入模式,回到命令模式。(记住:当你在命令模式 下按 <Esc> 时,vi 会蜂鸣。 如果你不确定自己处于哪种模式,按两次 <Esc>。听到蜂鸣时,你就知道 自己在命令模式了。)

共有十二条切换到输入模式的命令。 其中一半用于输入新数据;另 一半用于替换已有文本。当然你会问, 仅仅切换到输入模式为什么要这么多不同的 命令?答案是每条命令都在不同的 位置打开编辑缓冲区。因此,当你想插入数据时, 可以选择在当前情形下最合适的那个命令。下面就是这些命令:

i 切换到输入模式:在光标位置之前插入
a 切换到输入模式:在光标位置之后插入
I 切换到输入模式:在当前行开头插入
A 切换到输入模式:在当前行末尾插入
o 切换到输入模式:在当前行下方新开一行
O 切换到输入模式:在当前行上方新开一行

为了看清楚这是如何运作的,假设你 正在为《高级古典音乐》课编辑一篇学期论文。 你写的是著名歌词(*)的事,而 当前那一行恰好是:

For a dime you can see Kankakee or Paree

* 脚注

这一行取自 1939 年的歌曲 "Lydia the Tattooed Lady",由 Harold Arlen 和 Yip Harburg 创作。多年来,这首歌因 Groucho Marx 而广受欢迎,他总是一有机会 就喜欢唱它(有时甚至帽子都不戴)。

光标在字母 "K" 之下,而你处于 命令模式。如果你输入 i,就会切换到 输入模式。你输入时,数据会插在 "K" 之前。右边的字母会被推移,腾出位置。 例如,假设你输入:

iAAA<Esc>

(<Esc> 让你回到命令模式。) 当前那一行就会变成:

For a dime you can see AAAKankakee or Paree

现在,改为假设你按的是 a 来 切换到输入模式。这种情况下,数据会插在 "K" 之后。所以,假设你从原始那一行开始, 输入:

aBBB<Esc>

当前那一行现在就会是这样:

For a dime you can see KBBBankakee or Paree

— 提示 —

要记住 ia 命令的区别,可以这样想:i=insert(插入), a=append(追加)。

使用 I(大写 "I")和 A (大写 "A")命令,你可以分别在当前行的 开头或末尾插入数据。 例如,假设你从原始那一行开始, 输入:

ICCC<Esc>

当前那一行就会是这样:

CCCFor a dime you can see Kankakee or Paree

如果当前行用空格或制表符做了缩进, vi 会做出聪明的举动,从缩进之后 开始插入。

现在,假设你从原始那一行开始, 输入:

ADDD<Esc>

你输入的数据被追加到行尾。 当前那一行看起来是:

For a dime you can see Kankakee or PareeDDD

最后,要在当前行下方插入,用 o(小写字母 "o")命令。要在当前行上方 插入,用 O(大写 "O") 命令。两种情况下,vi 都会为你 新开一个全新的一行。

— 提示 —

要记住 oO 命令的区别,请记住两件事:

第一,字母 "o" 代表 "open"(打开)。

第二,想象命令名是一只充了氦气的气球。 大的那个气球 O 浮得更高,在当前行 上方。小的 气球 o 浮得更低,在当前行 下方。

当你在输入模式下工作时,有两件事我希望 你记住。正如本章前面讨论过的:

• 你可以使用 图 Figure 22-7 中列出的键来纠正错误,而不必 离开输入模式:<Backspace> (或 <Delete>)删除一个字符, ^W 删除一个词,^X(或 ^U)删除整行。

• 你可以在控制字符前加 ^V 来插入它; 例如,要输入一个退格符,按 ^V^H。 在屏幕上你会看到 ^H,尽管它 只是一个字符。

如我前面解释的,有许多命令可以移动 光标。特别地,^(抑字符) 命令移到当前行的开头(缩进之后);$(美元符号) 命令移到当前行的末尾。因此,如果 你想在当前行开头插入数据,你可以输入 ^ 再输入 i, 而不必用 I。类似地,你可以用 $a 代替 A 在行尾插入。

那么,这里就是对 vi 设计之美的 一个精彩说明。多学几个额外的 命令,你就能常常只输入一个字符(IA)而不是两个(^i$a)。如果 你是初学者,这也许算不上什么,但 用几天 vi 之后你就会看到, 凡是能减少常用操作按键次数的东西都是真正的便利。 当然,你得先学会那些额外的命令, 这就是我为什么说 vi 用起来简单, 学起来难。

如果你习惯用鼠标编辑,不要嘲笑 vi 较老的、以命令为中心的设计。我劝你 花些时间学会所有重要的 vi 命令。一旦你做到了,你会惊喜地发现, 双手不必离开键盘去移动鼠标或按 特殊键就能编辑数据是多么容易。 而且你会发现,使用 vi 强大的光标移动命令 比用鼠标去点滚动条轻松得多,也快得多(*)。

* 脚注

你可以通过观察一个人如何使用鼠标,来判断他 思维的速度。思路越顺畅(这叫 "ideaphoria",思想奔涌),这个人就越可能 偏爱键盘而不是鼠标。一般规律是, ideaphoria 高的人不喜欢把手从键盘上移开, 因为那会让他们慢下来。

你注意过别人浏览网页吗?ideaphoria 高的人会按 <PageUp>、<PageDown> 或 <Space> 键。 ideaphoria 低的人会用鼠标上下拖动滚动条。

— 提示 —

第一天就能上手使用的简单工具, 一个月之后就会显得笨拙别扭。

修改文本
(Changing Text)

上一节我们看了那些切换到输入模式的命令, 它们让你可以把数据插入编辑 缓冲区。本节我们要研究如何修改 已经在编辑缓冲区里的数据。首先,我 讲七个 vi 命令。其中只有一个 不是通过切换到输入模式来替换数据。我们先从 那个不切换到输入模式的命令开始。

要用另一个字符替换单个字符, 输入 r,后面跟新字符。 例如,假设你正在给某位教授写信, 解释你为什么没能写完学期论文。你处于命令模式, 当前那一行是:

would mean missing The Sopranos rerun. I gm sure you

你注意到 "gm" 这个词错了。把光标 移到 "g" 上,输入:

ra

当前那一行现在看起来是:

would mean missing The Sopranos rerun. I am sure you

由于你只改了一个字符,没有必要 进入输入模式。

然而,假设你想通过覆盖的方式替换多个 字符。移到你希望开始替换的位置,输入 R(大写 "R")。你会切换到输入模式,你输入的每个字符 都会替换当前行上的一个字符。结束时按 <Esc> 回到命令模式。这里举个例子。 当前那一行还是你上面留下的样子。你把 光标移到 "The" 中的 "T" 上,输入:

RMa's funeral<Esc>

当前那一行现在是:

would mean missing Ma's funeral. I am sure you

当你用 R 命令替换文本时, vi 不会离开当前行。因此, 如果你输入超过行尾,vi 只会把多出来的字符 追加到行尾。

有时你会想用大小并不完全相同的 数据去替换一个或多个字符。 有几个命令可以使用。s(substitute,替换)命令 让你用一个字符替换成多个字符。在我们的例子中, 把光标移到 "Ma" 中的 a 上,输入:

s

那个 a 会变成一个 $,而你 处于输入模式。你会看到:

would mean missing M$'s funeral. I am sure you

这个 $ 告诉你哪个字符正在被 替换。想输入多少就输入多少,完成后按 <Esc>。假设你输入:

other<Esc>

当前那一行现在是:

would mean missing Mother's funeral. I am sure you

C(大写 "C")命令是这类修改的一个 变体。它允许你替换从光标到行尾的 所有字符。在我们的例子中,假设你移到 "I" 上,输入:

C

你会切换到输入模式,最后一个将被替换的字符 用一个 $ 标出:

would mean missing Mother's funeral. I am sure you$

当前字符是 "I"。输入你想要的 内容,然后按 <Esc>。假设你输入:

We all hoped that<Esc>

当前那一行变成:

would mean missing Mother's funeral. We all hoped that

有时最简单的做法是替换整个 一行。有两个命令可以胜任: Scc。只要移到你想要替换的那一行,输入其中 任意一个命令。你就进入了输入模式。当你按 <Esc> 时, 你输入的内容将替换整行。

为什么会有两个完全相同的命令,名字却看起来 如此不同?vi 的许多命令名 遵循一种模式:有的名字是 一个小写字母,有的是两个小写字母,有的是一个大写字母。 按这个模式,Scc 都应当是 替换整行的命令。因此,哪个更说得通 就用哪个。(如果你现在 还看不出这个模式,别担心。等你再多学 几个命令就明白了。)

最后一个用于替换数据的 vi 命令 极为有用。这个命令是 c 后面跟 一个移动光标的命令。你同样会被 置于输入模式。只不过这一次, 你输入的内容会替换从光标起、直到移动命令所指示 位置之间的所有内容。这一点可能 有点绕,所以这里举几个例子。假设 当前那一行是:

would mean missing Mother's funeral. We all hoped that

光标在 "M" 上。你想把整个 单词 "Mother" 换成 "my dog"。输入:

cw

这会切换到输入模式,并用一个 $ 标记最后一个 将被替换的字符。你会看到:

would mean missing Mothe$'s funeral. We all hoped that

现在你输入:

my dog<Esc>

当前那一行变成:

would mean missing my dog's funeral. We all hoped that

因此,组合 cw 让你可以修改 一个词。你可以把 c 与任何单个字符的 光标移动命令搭配使用。如果需要,你还可以 加上重复计数。例如,命令 c5w 替换 5 个词。命令 c4b 从当前位置向后替换 4 个词。命令 c( 替换回句子的开头。命令 c} 替换到段落末尾。要替换 6 个 段落,移到第一个段落的 开头,输入 c6}

下面的汇总列出了 vi 的替换 命令:

r 精确替换 1 个字符(不进入输入模式)
R 通过覆盖输入来替换
s 以插入方式替换 1 个字符
C 以插入方式替换从光标到行尾的内容
cc 以插入方式替换整行
S 以插入方式替换整行
cmove 以插入方式替换从光标到 move 所指位置的内容

替换文本
(Replacing Text)

正如我们前面讨论过的,当你使用 vi 时,你既可以访问 vi(面向屏幕)命令,也可以访问更古老的 ex (面向行)命令。到目前为止,我们讲过的大多数命令都是 vi 命令。从本节开始,我们来讲解 ex 命令。

所有 ex 命令都以 :(冒号)字符开头。 每当你在命令开头键入一个冒号,vi 就会立刻把它 显示在命令行上(即屏幕底部)。当你继续键入命令的其余部分时, 它们会回显在这一行上(这就是它被称为命令行的原因)。 你会看到,ex 命令比 vi 命令更长、更复杂。 正因如此,vi 会在你键入时把它们回显出来,让你看清自己 在做什么。vi 中唯一既长又复杂的命令是搜索命令 (/?),这也是它们同样显示在命令行上的原因。

要替换某个特定的模式,你使用 ex 命令 :s (substitute,替换)。其语法是:

:s/pattern/replace/

其中 pattern 是你想要替换的模式,replace 是替换后的文本。 例如,要把当前行上的 "UNIX" 替换成 "Linux",使用:

:s/UNIX/Linux/

以这种方式使用时,:s 只会替换当前行上模式的第一个 匹配项。如果你想替换所有匹配项,就在命令末尾键入字母 g (global,全局)。例如,要把当前行上所有的 "UNIX" 都改成 "Linux", 你使用:

:s/UNIX/Linux/g

如果你希望 vi 在进行更改之前先征求你的许可,就在命令末尾 加上 c(confirm,确认):

:s/UNIX/Linux/c

当然,你可以同时组合 gc:

:s/UNIX/Linux/cg

当你使用 c 修饰符时,vi 会显示包含该模式的那一行, 指出模式的位置,然后等待你的决定。如果你想进行替换,就键入 y(代表 yes),然后按 <Return>。否则,键入 n<Return>(代表 no),或者干脆只按一次 <Return>。 (如果你没有指明 "y" 或 "n",vi 会审慎地假定你不想做这个更改。)

要删除一个模式,只要把它替换成"空"即可。例如,要移除当前行上 所有的 "UNIX",使用:

:s/UNIX//g

作为一种便利,如果命令末尾没有使用 cg, 你可以省略最后的 / 字符。举例来说,下面这两条命令是等价的:

:s/UNIX/Linux/
:s/UNIX/Linux

:s 命令有两个重要的变体。第一,你可以在冒号之后指定一个 具体的行号。这会告诉 vi 在那一行上执行替换。例如,要把 第 57 行上第一个 "UNIX" 改成 "Linux",使用:

:57s/UNIX/Linux/

(提醒:用 :set number 显示行号;用 :set nonumber 不显示行号。)

除了单个行号,你还可以用逗号分隔两个行号来表示一个范围。例如, 要在第 57 行到第 60 行上做同样的替换,使用:

:57,60s/UNIX/Linux/

在这种情况下,vi 会替换范围中每一行上指定模式的第一个匹配项。

大多数时候,你不会使用具体的行号。不过,有三个特殊符号能让这种 形式的命令特别有用。.(句点)代表当前行,$(美元符号) 代表编辑缓冲区中的最后一行。因此,下面这条命令会把 "UNIX" 的所有 匹配项替换成 "Linux",从当前行一直到编辑缓冲区末尾:

:.,$s/UNIX/Linux/g

要从编辑缓冲区的开头(第 1 行)到当前行做同样的更改,使用:

:1,.s/UNIX/Linux/g

第三个特殊符号是 %(百分号),它代表编辑缓冲区中的全部行。 因此,要把编辑缓冲区每一行上每个 "UNIX" 都改成 "Linux",使用:

:%s/UNIX/Linux/g

这等同于从第 1 行到第 $ 行(编辑缓冲区末尾)进行替换:

:1,$/UNIX/Linux/g

使用 % 比键入 1,$ 方便得多,所以一定要记住这个 好用的简写,你会经常用到它。

有时,你会希望 vi 在每次替换之前先征求确认。这样你就能 控制究竟替换哪些模式的匹配项。正如我们所说过的,你只需要使用 c(confirm,确认)修饰符即可,例如:

:%s/UNIX/Linux/cg

当你使用这样的命令时,可以通过按 ^C(intr 键)在 中途停止。这会中止整条命令,而不只是当前这一次替换。

作为参考,这里给出 :s 命令的一个汇总:

:s/pattern/replace/ 替换,当前行
:line/pattern/replace/ 替换,指定行
:line,lines/pattern/replace/ 替换,指定范围
:%s/pattern/replace/ 替换,所有行

在命令末尾,你可以用 cvi 征求确认,用 g (global,全局)替换每行上的所有匹配项。要指定行号,你可以用实际的 数字、代表当前行的 .(句点),或代表编辑缓冲区最后一行的 $(美元符号)。数字 1 代表编辑缓冲区中的第一行。

删除文本
(Deleting Text)

从编辑缓冲区中删除数据有若干种方法,既可用 vi 命令, 也可用 ex 命令。vi 命令如下:

x 删除光标处的字符
X 删除光标左边的字符
D 从光标处删除到行尾
dmove 从光标处删除到 move
dd 删除整个当前行

此外,还有两种 ex 命令的变体:

:lined 删除指定行
:line,lined 删除指定范围

无论你使用哪个命令,都可以通过撤销命令 uU (在下一节讨论)来撤销任何删除。记住这一点:总有一天它能救你的命。

最简单的删除命令是 x(小写 "x")。它删除当前光标位置处的 字符。例如,假设你正在给父母写信,告诉他们学校里生活的一切。 编辑缓冲区的当前行包含:

I love heiQnous paWrties and avoid the library as a rule

你注意到第三个词里有个错误。你把光标移到 "Q" 上,键入:

x

当前行现在变成:

I love heinous paWrties and avoid the library as a rule

X(大写 "X")命令删除光标左边的一个字符。例如,在上面 那一行里,你注意到第四个词里还有另一个错误。你移到 "r" 上,按下:

X

当前行现在变成:

I love heinous parties and avoid the library as a rule

I love heinous parties and avoid the library as a rule

D(大写 "D")命令从光标处删除到行尾。例如,假设你移到 "library" 一词后面的空格处,键入:

D

当前行变成:

I love heinous parties and avoid the library

下一个删除命令 d(小写 "d")后面要跟一个光标移动命令。 它从光标处删除到移动命令所指示的位置。这类似于我们前面讨论过的 c(change,改变)命令。这里有一些例子:

dw 删除 1 个词
d10w 删除 10 个词
d10W 删除 10 个词(忽略标点)
db 向后删除 1 个词
d2) 删除 2 个句子
d5} 删除 5 个段落

— 提示 —

有两种特别有用的 d 命令用法。第一,要删除从当前行到编辑 缓冲区末尾的所有行,使用 dG

第二,要删除从当前行到编辑缓冲区开头的所有行,使用 dggd1G。(正如我早先提到的,gg 命令在旧版本的 vi 上不工作。)

继续我们的例子,当前行仍然是:

I love heinous parties and avoid the library

你移到 "heinous" 一词的开头,键入以下内容删除 4 个词:

d4w

当前行变成:

I love the library

最后一个 vi 删除命令是 dd。它删除整个当前行。 如果你想删除多于一行,就在命令前面加一个重复次数。例如,要删除 单独一行,使用:

dd

要删除 10 行,使用:

10dd

有时你会发现用行号来删除更方便。为此,你使用 ex 命令 :d。要使用 :d 命令,你指定单个行号或一个范围 (两个用逗号分隔的数字)。例如,要删除第 50 行,使用:

:50d

要删除第 50 到 60 行,使用:

:50,60d

(提醒:要显示行号,使用 :set number;要关闭行号,使用 :set nonumber。)

与其他 ex 命令一样,符号 .(句点)代表当前行, $(美元符号)代表编辑缓冲区中的最后一行。因此,要删除从 编辑缓冲区开头到当前行的内容,使用:

:1,.d

这与 dggd1G 的效果相同。要删除从当前行到编辑 缓冲区末尾的内容,使用:

:.,$d

这与 dG 的效果相同。要删除整个编辑缓冲区,使用下面两条 命令中的任意一条:

:1,$d
:%d

记住,% 代表编辑缓冲区中的所有行。

撤销或重复一次更改
(Undoing or Repeating a Change)

一旦你开始进行替换和删除,能够撤销你的更改就变得很重要。 举例来说,假设你想把 "advertisement" 一词的所有出现都改成 "ad"。 你决定键入:

:%s/advertisement/ad/g

然而,你不小心打错了字,漏掉了第二个 "d":

:%s/advertisement/a/g

你刚刚把所有出现的 "advertisement" 都替换成了字母 "a"。 但是,你无法通过把所有 "a" 改成 "ad" 来弥补这个问题,因为到处都是 "a"。你可以使用 :q! 命令退出而不保存你的工作(如果你在处理 一个已有文件),但那样你就会丢失整个编辑会话中的所有更改。 你有什么办法可做吗?

这里有一个类似的情形。你想删除 10 行,但你没有键入 10dd, 而是键入了 100dd。你刚刚删除了 100 行。有什么办法把它们 找回来吗?

这两个问题的答案都是"有"。有两条命令可以用来撤销更改,还有一条 命令可以重复上一次更改:

u 撤销上一条修改编辑缓冲区的命令
U 恢复当前行
. 重复上一条修改编辑缓冲区的命令

u(小写 "u")命令会撤销上一条改变编辑缓冲区的命令: 一次插入、替换、改变或删除。在我们这两个例子里,你要做的只是 键入 u,替换/删除就会被取消。如果在按了 u 之后, 你又决定其实确实想要那个更改,只需再按一次 uu 命令能撤销它自己。(要是生活其余的部分也这么简单就好了。)(*)

* 脚注

只有在 vi 里生活才这么简单。在 Vim 中,连续多次按 u 会一条一条地撤销先前的命令。当然,在很多情况下,这也许正是你想要的。

U(大写 "U")命令会撤销你自上次移到当前行以来 对这一行所做的全部更改。例如,假设你把光标移到某一行,在这一行上 做了很多更改却没有离开它。不幸的是,你把它弄得一团糟,糟糕到你只 想让这一行恢复成你刚移过去时的样子。只要键入 U,这一行就会 恢复成它原来的内容。如果在按了 U 之后你不喜欢这个结果, 你可以用 u(小写 "u")命令撤销它。

U 命令会一次性撤销恢复当前行所需的全部更改。然而, U 只在你停留在该行期间有效。一旦你把光标移到新的一行, U 命令就会作用于那一行,而且没有办法轻松地恢复旧的那一行。

除了 uU,还有另一条与编辑缓冲区上一次更改有关的 重要命令。它就是 .(点)命令。你用它是为了重复上一条修改 编辑缓冲区的命令。这条命令可能非常有用,那我们来看一个例子。

假设你想在编辑缓冲区的几个不同位置插入 "Mxyzptlk" 这个名字。 这个名字很难拼,要键入它两次以上很麻烦,所以这里有个聪明的做法。 移到你想做第一次插入的位置,键入:

iMxyzptlk<Esc>

你已经把这个名字插入了编辑缓冲区。现在,移到你想做同样插入的下一个 位置,键入:

.

完全相同的插入会为你重复一遍。你可以根据需要多次使用 . 命令。不过要小心:一旦你做了另一次更改,哪怕只是删除一个字符这么 微小,那么 . 命令的效果也会随之改变。

找回删除的内容
(Recovering Deletions)

每当你删除一行或多行文本时,vi 都会把这次删除保存到一个 特殊的存储区域,叫做编号缓冲区(NUMBERED BUFFER)。这样的缓冲区有 9 个,编号从 1 到 9。任何时候,你都可以把一个编号缓冲区的内容 插入到编辑缓冲区中。为此,键入一个 "(双引号),后面跟上 缓冲区的编号,再跟上一个 pP(put,放置)命令。 (提醒:当你在处理整行时,p 命令插入到当前行下方;P 命令插入到当前行上方。)

例如,要把 1 号缓冲区的内容插入到当前行下方,你使用:

"1p

要把 2 号缓冲区的内容插入到当前行上方,你使用:

"2P

用这种方式,你可以找回并插入你最近 9 次删除中的任意一次。 比方说,你做了若干次删除,想恢复其中的一次。然而,你记不清 是哪一次了。

先键入 "1p。如果它没有给出你想要的文本,就键入 u 撤销这次插入,然后试试 "2p。如果还是不行,就键入 u 撤销这次插入,然后试试 "3p。一直试下去直到你得到想要的为止。 这个序列看起来会像这样:

"1pu"2pu"3pu...

这本身就已经相当酷了。不过,vi 还能做得更多。在你撤销 第一次插入之后,如果你用 .(点)命令来重复这次插入, vi 会自动把缓冲区编号加 1。这意味着,你不必使用上面那个 序列,而可以使用:

"1pu.u.u...

要验证这一点,用 vi 创建一个包含下面五行的文件:

111
222
333
444
555

键入 1Ggg 跳到第一行。然后连续键入五次 dd,分别删除每一行。现在试试上面那个找回序列,看看会发生什么。

提醒:编号缓冲区只保存删除的整行,不保存行的一部分或单个字符。 例如,如果你用 10dd 删除 10 行,这次删除会被保存到某个编号 缓冲区。然而,如果你用 5x 删除 5 个字符,这次删除就不会以这 种方式保存。

移动文本
(Moving Text)

vi 编辑器有一种特殊的机制,让你能够把文本从一个地方移动 或复制到另一个地方。本节我们讲移动。下一节我们讲复制。

任何时候,vi 都会把你的最后一次删除保存一份副本在一个叫 做未命名缓冲区(UNNAMED BUFFER)的存储区域中。任何时候,你都可以通过 使用 pP(put,放置)命令,把未命名缓冲区的内容 复制到编辑缓冲区中。(这个存储区域之所以被称为未命名缓冲区,是因为 还有其他类似的、带有名字的存储区域。)

p(小写 "p")命令把未命名缓冲区的内容插入到光标当前位置 之后。例如,假设当前行包含:

This good is a sentence.

你移到 "g" 上并删除一个词。你这样做时,那个词被复制到了未命名 缓冲区:

dw

当前行现在看起来是这样:

This is a sentence.

现在你移到 "a" 后面的空格处,键入:

p

未命名缓冲区的内容被插入到光标右侧。当前行变成:

This is a good sentence.

这里有一个使用 P(大写 "P")命令的例子。假设当前行包含:

This is right now.

你移到 "right" 一词前面的空格处,键入:

de

这会擦除到词的结尾为止,留下:

This is now.

现在移到这一行末尾的句号处,键入:

P

这次删除被插入到光标左侧。当前行变成:

This is now right.

重要的是要理解,未命名缓冲区一次只能容纳一样东西。例如,假设你 刚刚删除了 1000 行文本。文本的一份副本存储在未命名缓冲区里。如果 你愿意,你可以把它插入到编辑缓冲区的别处。这时,你删除了一个单独的 字符。那 1000 行文本就被冲出了未命名缓冲区。如果你现在使用 p 命令,你得到的将是你最后一次删除的内容,也就是那个单独的字符。

继续往下说,想一想 xp 这个组合。x 命令删除当前光标 位置处的字符。p 命令把这次删除插入到光标右侧。最终的结果是 对调了两个字符。例如,假设当前行是:

I ma never mixed up.

你移到第一个 "m" 上,键入:

xp

当前行现在是:

I am never mixed up.

另一个重要的组合是 deep,你可以用它来对调两个词。这里有一 个例子。假设当前行包含:

I am mixed never up.

移到 "mixed" 一词前面的空格处。(注意要移到词前面的空格,而不是 词的第一个字母上。)现在键入:

deep

de 命令删除这个空格以及紧随其后的词,之后当前行看起来像:

I am never up.

第二个 e 命令向前移到下一个词的结尾。接着 p 把这次 删除插入到光标之后。最终结果是:

I am never mixed up.

就这样,你用 deep 对调了两个词。现在就花一点时间把这个组合 记熟,好让你在需要时能够迅速地键入它。

每当你删除整行时,pP 就会插入整行。p 命令插入到当前行下方;P 插入到当前行上方。例如,假设你想把 10 行从一个地方移到另一个地方。首先,把光标移到第一行文本上。然后用 带重复次数的 dd 命令删除 10 行:

10dd

这些行从编辑缓冲区中被删除,并复制到未命名缓冲区中。现在把光标移到 你想在其下方做插入的那一行,键入:

p

现在想一想你键入 ddp 时会发生什么。dd 命令删除当前行。 下一行成为新的当前行。p 把这次删除插入到新当前行下方。最终 结果是对调了两行。(试一试。)

归纳一下:

p 复制最后一次删除;插入到光标之后/之下
P 复制最后一次删除;插入到光标之前/之上
xp 对调两个字符
deep 对调两个词(把光标起始位置放在第一个词的左侧)
ddp 对调两行

复制文本
(Copying Text)

把文本从一个位置复制到另一个位置是一个三步过程。第一,你使用 yyyY 命令,把文本从编辑缓冲区复制到 未命名缓冲区,而不删除原文本。第二,你把光标移动到你想要插入文本的 地方。最后,你用 pP 完成插入。

当你把文本复制到未命名缓冲区而不删除它时,我们说你"抽取(yank)"了 这段文本。(因此才有 yyyY 这些名字。) yyy 命令的工作方式与 ddd 完全 相同,只是它们抽取而不是删除。这里有一些例子:

yw 抽取 1 个词
y10w 抽取 10 个词
y10W 抽取 10 个词(忽略标点)
yb 向后抽取 1 个词
y2) 抽取 2 个句子
y5} 抽取 5 个段落
yy 抽取 1 行
10yy 抽取 10 行

假设你想把 5 个段落从一个地方复制到另一个地方。首先,把光标移到 第一个段落的开头。接下来,把 5 个段落到抽取未命名缓冲区中,而不 删除文本:

y5}

现在把光标移到你想在其下方做插入的那一行,插入文本:

p

为了方便,你可以把 Y 当作 yy 的同义命令来使用。 因此,下面两条命令都会把 10 行抽取到未命名缓冲区中:

10yy
10Y

注意一件有趣的事。y 命令与 d 相类似,它们都把文本 复制到未命名缓冲区,范围是从当前字符到一次光标移动的终点。 (唯一的区别是 d 删除而 y 抽取。)类似地,yydd 相类似,它们都是对整行进行删除/抽取。

然而,Y 命令的工作方式与 D 并不相同。Y 命令 抽取整行。D 命令从当前字符删除到行尾。如果你想从当前字符抽取 到行尾,你必须使用 y$。要从当前字符抽取到行首,你会使用 y0

— 提示 —

每当你删除或抽取文本时,文本都会留在未命名缓冲区里,直到你输入 另一条删除或抽取命令。因此,你可以使用 pP 命令, 把同一段文本一次又一次地插入到编辑缓冲区内的不同位置。

改变字母的大小写
(Changing the Case of Letters)

vi 编辑器有一条专门的命令,用来把字母从小写改成大写,或从 大写改成小写。这条命令是 ~(波浪号)。只要把光标移到你想要 更改的字母上,按下:

~

~vi 改变当前字符的大小写,然后把光标前移一个位置。 例如,假设当前行包含:

"By Jove," he said, "that's a CAPITAL idea."

光标在 "C" 上。你按下 ~。当前行现在看起来是:

"By Jove," he said, "that's a cAPITAL idea."

光标现在在 "A" 上。由于 ~ 会把光标右移一个位置,你可以反复 键入 ~ 来更改一串字母。在我们的例子里,你可以再键入六个波浪号 把这个词的其余部分改成小写:

~~~~~~

当前行变成:

"By Jove," he said, "that's a capital idea."

如果光标停在一个不是字母的字符上(比如一个标点符号)时你键入 ~,vi 会前移光标,但不会做任何更改。因此,你可以放心 地一路"波浪"过去,跨越很长一段距离,因为 vi 会直接跳过那些非 字母字符。为了让这更容易,你可以在这个命令前面加上一个重复次数。 例如,要更改一个 7 个字母的词的大小写,把光标移到词的开头,键入:

7~

整个词的大小写都会被更改,而光标停在词末尾之后的一个位置。

注意:在某些版本的 vi 中,即使你使用较大的重复次数(比如 100~),~ 命令也不会越过当前行的末尾。其他版本的 vi 会处理你所指定的那么多个字符,甚至可以跨越多行。当你有 空闲时间时,不妨用你的 vi 版本做个实验,看看 ~ 命令 会不会越过行的末尾。

设置选项
(Setting Options)

和大多数复杂的 Unix 程序一样,vi 支持许多选项(OPTIONS), 让你能够控制它运行的各个方面。当你启动 vi 时,每个选项都会被 赋予一个默认值。如果你想改变 vi 行为中的某个方面,你可以用 :set 命令来设置相应选项的值。语法有两种形式,因为选项有两种 不同的类型:

:set [no]option...
:set option[=value]...

其中 option 是选项的名字,value 是选项应有的值。

在大多数情况下,默认值就够用了(这也是它们成为默认值的原因)。然而, 有时你可能想做一些更改。本章中我们已经这样做了三次。第一,我们用 showmode 选项告诉 vi,每当我们处于输入模式时显示一个 提醒。我们用的命令是:

:set showmode

我们用的第二个选项是 number,用来显示行号:

:set number

最后,我们用 nonumber 关闭编号:

:set nonumber

vi 选项有两种类型。第一种是开关(SWITCHES),它们要么关闭 要么开启。我刚提到的那些选项全都是开关。要打开一个开关,你用它的名字。 要关闭一个开关,你在名字前面打上 "no"。例如:

:set showmode
:set noshowmode
:set number
:set nonumber

第二种选项是变量(VARIABLES),它们包含一个值。举例来说,tabstop 变量设置制表间距。默认情况下,tabstop 设为 8,这意味着制表符 会扩展到每第 8 个位置(与一般的 Unix 相同;见第 18 章)。如果你想让 制表符扩展到,比如说,每第 4 个位置,你会把 tabstop 变量设为 4:

:set tabstop=4

作为一种便利,在同一条命令中设置多个选项是可行的,例如:

:set showmode nonumber tabstop=4

实际可用的选项取决于你使用的是哪个版本的 vi。正如你所预料的, 较新的版本有更多的选项。通常,标准的 vi 大约有 40 个左右的选项, 其中 16 个是重要的。Vim 有超过 340 个选项,几乎全是你永远用不到的。 作为参考,图 22-10 展示了重要的 vi 选项。

图 22-10:vi 选项:开关和变量

你可以通过两种不同类型的选项来控制 vi 的各个方面:开关 (关闭或开启)和变量(保存一个值)。这两份列表展示了最有用的开关 和变量,以及它们的缩写、默认值和含义。

开关 缩写 默认值 含义
autoindentaioff缩进时与上一行/下一行对齐
autowriteawoff若文本已被修改,在切换文件前先保存
errorbellseboff显示错误消息时蜂鸣
exrcexoff在当前目录中查找初始化文件
ignorecaseicoff搜索时忽略大小写
listoff把制表符显示为 ^I;行尾显示为 $
numbernuoff显示行号
readonlyrooff不允许改变编辑缓冲区的内容
showmatchsmoff输入模式:显示相匹配的 (){}[]
showmodesmdoff处于输入模式时显示提醒
wrapscanwsoff搜索时环绕文件的末尾/开头
writeanywaoff允许写入任何文件,无需 ! 来强制覆盖
变量 缩写 默认值 含义

正如你从这两张表中所能看到的,几乎所有选项都有缩写。为了你的便利, 你可以使用这些缩写而不必键入完整的名字。例如,下面两条命令是等价的:

:set showmode nonumber tabstop=4
:set smd nonu ts=4

— 提示 —

要在每次启动 vi 时自动设置选项,把相应的 :set 命令 放进一个初始化文件(本章后面会讨论)。

显示选项
(Displaying Options)

要显示一个或多个选项的值,你使用 :set 命令的一个变体。 语法是:

:set [option[?]... | all]

其中 option 是选项的名字。

要显示所有选项的值,使用:

:set all

使用这条命令是查看你的 vi 版本所支持的全部选项列表的最佳方式。 要显示单个选项的值,键入选项名字后跟一个 ?(问号)。例如:

:set number?
:set showmode?
:set wrapmargin?

作为一种便利,在同一条命令中显示多个选项名的值是可行的:

:set number? showmode? wrapmargin?

最后,要只显示那些已经偏离默认值的选项的值,单独使用 :set:

:set

当你使用最后这条命令时,你可能会看到一些你不记得改过的选项。 这是因为每个系统都有若干初始化文件,在 vi 启动时会被它读取。 其中有一个文件是受你控制的,你可以用它来按需设置你的工作环境。 (本章后面我们会讲到这一点。)其他的初始化文件要么是在 vi 安装时自动生成的,要么是由你的系统管理员设置的。通常,这些文件会 包含修改某些选项值的命令,这些选项因而会出现在非默认值选项的列表里。

在输入时自动折行
(Breaking Lines Automatically As You Type)

当你键入一篇文档时,你需要把文本分成若干行。一种做法是在每一行 末尾按 <Return>。正如我在第 7 章中所解释的,按 <Return> 会产生一个换行符,它标记一行的结束。这对少量文本来说没问题,但如果你 要键入很多内容,让 vi 自动为你折行会更方便。为此,你设置 wrapmargin(wm)选项。语法是:

:set wrapmargin=n

其中 n 是你希望行开始折断处距右边距的位置数。为了方便,你可以 用缩写 wm 来代替全名。

wrapmargin 选项只影响输入模式。当你把 wrapmargin 设为 大于 0 的数时,它会让 vi 在你键入的过程中,当一行延伸到距右边距 那么多个字符之内时,把一行断成两行。例如,要告诉 vi 当你的行 延伸到距右边距 6 个字符之内时自动折行,使用下面两条命令中的任意一条:

:set wrapmargin=6
:set wm=6

如果你想要尽可能长的行,使用值 1:

:set wm=1

要关闭自动折行,把 wm 设为 0:

:set wm=0

如果你在处理带缩进的文本,你可以打开 autoindent(ai) 选项:

:set autoindent

这会告诉 vi 让缩进与你正在键入的那一行的上一行或下一行对齐。

自动折行只影响你正在键入的文本。要重新格式化已有的文本,你可以使用 rJ 命令(见下一节),或者使用第 18 章中的 fmt 命令(本章后面会讨论)。

— 提示 —

如果你把 wrapmargin 设为小于 6 的任何值,每一行末尾就只有 很少的空间,这使得改正起来很别扭。以我的经验,wrapmargin 最好的设置是在 6 到 10 之间,这样为小的改动留出了充足的空间。

折断行与合并行
(Breaking and Joining Lines)

很多时候你会想把长行断成两行,或把短行合并起来。例如:

This line is much too long and must be broken into two.

你想在 "and" 一词之后折断这一行。最简单的方法是把光标移到 "and" 之后的空格处,键入:

r<Return>

使用 r 命令会用一个字符替换另一个字符。在这个情形里,r 命令用一个换行符替换了那个空格,从而把这一行断开了。

如果有些行太短,你可以把光标移到第一行上,然后键入 J(大写 "J")来把它们合并。这会把当前行和下一行合成一条长行。当 vi 合并行时,它会在合适的位置自动插入空格:词与词之间一个空格,句末 两个空格。(是不是很贴心?)

要同时合并多于两行,在 J 命令前面加上一个重复次数。这里有一 个例子。你的编辑缓冲区包含以下几行:

This sentence
is short.
This sentence is also short.

把光标移到第一行,键入:

3J

结果是:

This sentence is short. This sentence is also short.

— 提示 —

rJ 命令很适合做小的调整。然而,当你需要重新格式化 超过 5-6 行文本时,通常更好的做法是使用 fmt 命令,本章后面会 解释。

复制与移动行
(Copying and Moving Lines)

很多时候,你会发现用行号来复制或移动行很方便。对于这些操作,你使用 ex 命令 :co(copy,复制)和 :m(move,移动)。 两条命令使用相同的格式。唯一的区别是 :m 会删除原来的行,而 :co 不会。

要使用这些命令,你在命令名前面指定单个行号或一段行号范围。这些就是要 被复制或移动的行。在命令名之后,你指定目标行号。新的行会被插入到目标行 下方。语法是:

x[,y]:coz
x[,y]:mz

其中 xyz 都是行号。

源行(x,或 xy)被复制或移动,并插入到目标行 (z)下方。这里有一些例子:

:5co10 复制第 5 行,插入到第 10 行下方
:4,8co20 复制第 4 到 8 行,插入到第 20 行下方
:5m10 移动第 5 行,插入到第 10 行下方
:4,8m20 移动第 4 到 8 行,插入到第 20 行下方

(提醒:要显示行号使用 :set number;要关闭行号,使用 :set nonumber。)

与其他 ex 命令一样,你可以用 .(句点)来指代当前行, $(美元符号)来指代编辑缓冲区中的最后一行。例如,下面这条命令 把第 1 行到当前行移动到编辑缓冲区的末尾:

:1,.m$

你也可以用行 0(零)来指代编辑缓冲区的开头。例如,下面这条命令把 从当前行到最后一行的内容移动到编辑缓冲区的开头:

:.,$m0

最后这两条命令很有意思。它们都会交换编辑缓冲区的上半部分和下半部分。 然而,二者有一个细微的差别。用第一条命令,当前行最终会落到编辑缓冲区 的底部。用第二条命令,当前行最终会落在顶部。(花点时间想一想这一点。)

输入 Shell 命令
(Entering Shell Commands)

有好几种方法可以在不中断 vi 的情况下使用普通的 Shell 命令。 第一,你可以通过键入 :! 后跟命令来输入一条命令。这告诉 vi 把命令送给 Shell 去执行。当命令结束时,控制权会返回给 vi。例如,要显示时间和日期,输入:

:!date

命令结束后,你会看到一条消息。消息的内容因你的 vi 版本而异。 以下是最常见的几种:

Press ENTER or type command to continue
[Hit return to continue]
Press any key to continue

到了这一步,只要按 <Return>,你就回到了 vi。要重复最近 一次输入的 Shell 命令——无论你多久以前输入的它——使用:

:!!

例如,如果你输入的最后一条 Shell 命令是 date,你可以用 :!! 再次显示时间和日期。

要把一条 Shell 命令的输出直接插入到编辑缓冲区中,你可以使用 :r! 命令。我们会在下一节讲这条命令,在那里你会找到一些例子。

有时,你会想输入多条 Shell 命令。你可以通过启动一个新的 Shell 来 做到这一点。有两种做法。第一,你可以使用 :sh 命令:

:sh

这会暂停 vi 并启动一个你默认 Shell 的新副本。你现在可以输入 任意多条命令。当你用完 Shell 时,通过按 ^D 或输入 exit 命令来停止它。你会被返回到 vi

或者,你可以通过运行一个实际的命令来启动一个新的 Shell。例如,要启动 一个新的 Bash Shell,运行 bash 程序:

:!bash

要启动一个新的 Tcsh Shell,使用:

:!tcsh

(各种 Shell 在第 11 章中讨论。)和 :sh 一样,当你结束 Shell 时, 你会被返回到 vi。当出于某种原因你想使用一个非默认的 Shell 时, 这个技巧很方便。例如,假设你通常用 Bash,但就这一次,你想通过运行 Tcsh 来测试某个东西。你要做的只是使用命令 :!tcsh

归纳一下:

:!command 暂停 vi,执行 Shell 命令
:!! 暂停 vi,执行上一条 Shell 命令
:sh 暂停 vi,启动一个新的(默认)Shell
:!bash 暂停 vi,启动一个新的 Bash Shell
:!tcsh 暂停 vi,启动一个新的 Tcsh Shell

把文件中的数据
插入编辑缓冲区
(Inserting Data From a File
Into the Editing Buffer)

要把数据从一个已有的文件读入编辑缓冲区,你使用 :r 命令。 语法是:

:[line]r file

其中 line 是行号,file 是文件名。

:r 命令读取文件的内容,并把它插入到指定行的后面。例如, 下面这条命令把文件 info 的内容插入到第 10 行之后:

:10r info

要指代编辑缓冲区的开头,使用行 0(零)。例如,要在编辑缓冲区的开头 插入 info 的内容,使用:

:0r info

要指代编辑缓冲区的末尾,使用 $。例如,要在编辑缓冲区的末尾 插入 info 的内容,使用:

:$r info

如果你省略行号,vi 会把新数据插入到当前行之后。这也许是 :r 命令最有用的形式。你只需把光标移到你想插入新数据的地方, 然后输入 :r 命令。例如,假设你想在当前段落的末尾插入文件 info 的内容。用 }(右花括号)命令跳到段落的末尾,然后 插入数据:

}
:r info

把 Shell 命令的输出
插入编辑缓冲区
(Inserting the Output of a Shell Command
Into the Editing Buffer)

:r 命令有一个特别有用的变体。如果你输入的不是文件名,而是一个 !(感叹号)后跟一个程序名,vi 就会执行该程序并把它输出的 内容插入编辑缓冲区。这里有一个例子。ls 程序显示你工作目录中文件 的列表(见第 24 章)。要在当前行之后插入这样一份列表,输入:

:r !ls

作为第二个例子,这里是你如何在编辑缓冲区第一行的上方插入当前时间和 日期:

:0r !date

为了完整起见,关于 :r,这里有一个能省下宝贵时间的绝妙主意,它 展示了这条命令的强大。在第 19 章里,我们讨论过如何用 look 命令 来帮助拼写。例如,假设你想用 "ascetic" 这个词,但你不太确定该怎么拼。 你可以用 look 从词典文件里显示可能的词:

look asc

下面是一些典型的输出。快速浏览这份列表就能看出你要的那个确切的词 (在第 5 行):

ascend
ascendant
ascent
ascertain
ascetic
ascribe
ascription

假设你正在用 vi,往编辑缓冲区里键入,到了你想把这个特定的词 插入文本的地方。按 <Esc> 从输入模式切换到命令模式,然后输入命令:

:r !look asc

look 命令的输出会被插入到当前行(你最后键入的那一行)之后。 看看这个列表,删除所有你不想要的词。在这个例子里,你会删除除第 5 个词 以外的所有词。(如果你一个词都不想要,你可以用 u 命令撤销插入。) 一旦你删除了除正确词以外的所有词,移到你最后键入的那一行,键入:

J

这会把新词接到那一行的末尾。最后,要回到输入模式、准备好在行尾插入 文本,键入:

A

这让你能够在当前行末尾追加数据。你现在又回到工作状态了:继续键入。

起初,像上面这样的命令序列可能看起来有点复杂。实际上,一旦你习惯了, 它就相当简单。而且,你能做到全程双手不离键盘。试一试:它非常酷。

这里是 :r 命令的汇总:

:liner file file 的内容插入到指定行之后
:r file file 的内容插入到当前行之后
:liner !program program 的输出插入到指定行之后
:r !program program 的输出插入到当前行之后

— 提示 —

如果你不喜欢某次 :r:r! 命令的结果,你可以用 u(undo,撤销)命令把它逆转。

用程序处理数据:fmt
(Using a Program to Process Data: fmt)

!!!(感叹号)命令会把编辑缓冲区中的行送给另一个 程序。该程序的输出会替换原来的那些行。例如,你可以用排序后的同样数据 来替换一组行。它是这样工作的。

移动到你想要开始的那一行。键入你想要处理的行数,后跟 !!(两个 感叹号),再后跟一个程序名,最后跟 <Return>。例如,假设你有 5 行, 包含以下你想排序的数据。(sort 程序在第 19 章中讨论。)

entertain
balloon
anaconda
dairy
coin

移到第一行,输入:

5!!sort

一旦你键入第二个 !,vi 就会把光标移到命令行并显示一个 ! 字符。你现在可以直接在命令行上键入任何 Shell 命令。必要时, 你可以在按 <Return> 之前按 <Backspace>(或 <Delete>) 来做更正。在我们的例子里,原来的 5 行会被替换成:

anaconda
balloon
coin
dairy
entertain

如果你不喜欢这些结果,和往常一样,你可以用 u 命令撤销这个更改。

这里还有另一个你会觉得特别有用的例子。在第 18 章里,我们讨论过 fmt(format,格式化)程序。这个程序把文本重新格式化成(默认) 不超过 75 个字符的行。在此过程中,fmt 会保留行首的空格、词与词 之间的空格以及空行。换句话说,fmt 会让你的文本看起来整洁,而不 改变段落分隔。

当你创建文档时,fmt 程序很适合用来格式化你编辑缓冲区的全部或 一部分。一旦你知道了怎么使用 fmt,你在输入或修改数据时就不必太 担心换行的问题,因为你总能事后把它们修正。作为一个例子,下面这条命令 会从当前行开始格式化 10 行:

10!!fmt

到目前为止,我们所有的例子都用了 !!(双感叹号)命令。! (单感叹号)命令的工作方式大同小异,只是它在指定输入行的范围时给了你 更多的灵活性。

键入 !,后跟一个移动光标的命令,再后跟一个程序名。从当前行到 光标移动终点的所有行都会被送给该程序处理。例如,假设你想格式化从当前行 到段落末尾的文本。记住 } (右花括号)命令会把光标移到段落的末尾, 你会使用:

!}fmt

本章后面,我会向你展示如何让这条命令用起来格外方便。(见关于宏的那一节。)

继续往下说,这里有一个格式化整个编辑缓冲区的简单方法。键入 gg1G 跳到编辑缓冲区的第一行。然后输入:

!Gfmt

(记住,G 命令会跳到编辑缓冲区的末尾。)类似地,你可以用 gg1G,后跟以下内容来排序整个编辑缓冲区:

!Gsort

归纳一下:

n!!program n 行执行 program
!move program 对从当前行到 move 的范围执行 program

把数据写入文件
(Writing Data to a File)

当你用 ZZ 命令退出 vi 时,它会自动保存你的数据。然而, 有几条命令可以让你随时把数据写入文件。这些命令很重要,因为它们让你能够 不时地备份你的数据而无需退出 vi。它们还让你能把数据保存到一个 不同的文件。这些命令是:

:w 把数据写入原文件
:w file 把数据写入一个新文件
:w! file 覆盖一个已有的文件
:w>> file 把数据追加到指定文件

:w 命令把编辑缓冲区的内容写入原文件,替换该文件的当前内容。 例如,假设你通过输入下面这个命令启动 vi:

vi memo

文件 memo 的内容被复制到编辑缓冲区。无论你对编辑缓冲区做了多少 更改,原文件 memo 都不会改变。这一点很重要,因为它让你能够在不 改变你的文件的情况下退出(使用 :q! 命令)。然而,任何时候,你都 可以通过输入以下内容把编辑缓冲区的内容复制到原文件:

:w

通常,除非你要用 :e 命令开始编辑一个新文件(见下文),否则你不 需要这样做。然而,如果你已经做了很多更改,你可能想花点时间把它们保存到 原文件。这能保护你不因出了什么岔子而丢失你的工作。

如果你在 :w 命令后指定了一个文件名,vi 会把数据写入那个 文件。例如,要把编辑缓冲区的内容保存到一个名为 extra 的文件, 输入:

:w extra

如果这个文件尚不存在,vi 会创建它。如果这个文件已存在,vi 会显示一条警告消息。以下是两条典型的消息:

File exists - use "w! extra" to overwrite
File exists (add ! to override)

如果你确实想覆盖这个文件,你必须改用 :w!:

:w! extra

要把数据追加到一个已有文件的末尾,在命令名后面键入 >> (两个大于号)。例如:

:w>> extra

使用 >> 会保留旧数据。注意它与用于把标准输出追加到已有 文件的 >> 记法很相似(见第 15 章)。

如果你只想写入编辑缓冲区中某些特定的行,你可以用通常的方式来指定它们。 例如,要把第 10 行写入一个名为 save 的文件(替换 save 的 内容),输入:

:10w save

要把第 10 到 20 行追加到名为 save 的文件,使用:

:10,20w>> save

本章前面,我解释过退出 vi 有两种方式。要保存你的数据并退出,你 使用命令 ZZ;要不保存就退出,你使用 :q!。实际上,还有第三 种方式。只要你已经用过 :w 命令保存了你的数据,你就可以用 :q 退出。作为一种便利,你可以把这两条命令组合起来:

:wq

因此,组合 :wqZZ 有相同的效果。

切换到新文件
(Changing to a New File)

当你启动 vi 时,你可以指定你想编辑的文件名。例如,要编辑一个 名为 memo 的文件,你会输入:

vi memo

如果你决定编辑另一个不同的文件,你不必退出程序再重启它。要切换到一个 新文件,使用 :e 命令,后跟文件名。例如,要编辑一个名为 document 的文件,输入:

:e document

当你开始编辑一个新文件时,编辑缓冲区之前的内容就会丢失,所以一定要先 用 :w 命令保存你的数据。当你使用 :e 命令时,vi 会检查你是否已经保存了数据。如果有未保存的数据,vi 不会让你切换 到新文件。如果你想绕过这一保护,使用 :e! 命令。例如,假设你用 下面这个命令启动 vi:

vi memo

memo 的内容被复制到编辑缓冲区。恰在此时,你犯了太多错误,宁愿 从头再来。你最不想做的事就是把编辑缓冲区的内容保存回原文件。只要输入:

:e!

现在你正在编辑的是原 memo 文件的一份副本:先前的更改都被丢弃了。 归纳一下:

:e file 编辑指定文件
:e! 重新编辑当前文件,跳过自动检查
:e! file 编辑指定文件,跳过自动检查

使用缩写
(Using Abbreviations)

要为常用的词或短语创建缩写,你使用 :ab(abbreviate,缩写)命令。 语法是:

:ab [short long]

其中 short 是缩写,long 是该缩写的替换内容。

这里有一个例子。你正在为一份暑期工作写简历,你发现一遍又一遍地键入 "exceptionally gifted" 令人厌烦。取而代之,你可以建立一个缩写,比方说 "eg"。键入 :ab,后跟短形式,再后跟长形式:

:ab eg exceptionally gifted

从今往后,只要你在输入模式下把 eg 作为一个独立的词键入, vi 就会自动把它替换成 "exceptionally gifted"。注意,这个替换 只在 "eg" 单独成词时才发生;vi 足够聪明,不会去替换另一个词内部 的 "eg",比如 "egotistical"。

要移除一个缩写,使用 :una(un-abbreviate,取消缩写)命令。 语法是:

:una short

其中 short 是一个缩写。只要键入 :una,后跟你想移除的短 形式的名字。例如:

:una eg

任何时候,你都可以通过单独输入 :ab 命令来查看你所有缩写的列表:

:ab

— 提示 —

要在每次启动 vi 时自动定义缩写,把相应的 :ab 命令放进 你的初始化文件(本章后面会讨论)。


(Macros)

正如我们在上一节所讨论的,:ab 命令让你能够创建在输入模式中 使用的缩写。你不必一遍又一遍地键入同样的文本,而可以用一个缩写。类似地, :map 命令让你能够创建在命令模式中使用的单字符缩写。这实际上让 你能够创建你自己的定制单字符命令,这些命令叫做宏(MACROS)。语法是:

:map [x commands]

其中 x 是单个字符,commands 是一串 viex 命令。

这里有一个简单宏的例子。本章前面(在关于移动文本的那一节),我向你展示 过如何对调两个词:把光标移到第一个词前面的空格,键入 deep。为了 让这更方便,你可以定义一个宏:

:map K deep

现在,要对调任意两个词,你要做的只是把光标移到第一个词前面的空格, 然后按 K

根据定义,宏的名字必须是单个字符。如果你用一个已经有含义的名字,那个 字符就会失去它的含义。例如,本章前面,我们讲过 x(小写 "x")和 X(大写 "X")命令。x 命令删除当前字符(光标所指的那个字符); X 删除光标左边的字符。考虑下面这个宏定义:

:map X dd

这条命令创建了一个名为 X(大写 "X")的宏,它删除当前行。一旦你 定义了这个宏,普通的 X 命令就没了。不过,你可能并不介意。如果你 很少用那个命令,你可能会觉得键入 X 来删除一行挺方便。

然而,通常来说,不去替换常规命令是个好主意。这就引出了一个问题:哪些 字符是 vi 或 Vim 没有使用的?实际上非常少。正如你能从 图 22-11 中看到的那样,只有 14 个字符是 vi 没有使用的。Vim 更是 极端:它不作为命令名使用的普通字符只有 ^K\(反斜杠)。 这并不像你想象的那么受限,因为有好几条 Vim 命令你大概永远用不到,所以 替换掉它们是安全的。这些字符也在图 22-11 中列了出来。

图 22-11:可用作 vi 和 Vim 宏名的字符

:map 命令让你能够创建一个宏,也就是一串 viex 命令的缩写。宏的名字必须是单个字符。如果你选一个已经作为命令而有含义的 名字,那个含义就会丢失。第一张表展示了那些没有被用作 vi 命令的 字符,因而可以安全地用作宏名。

vi:可用作宏名的字符
字母 g  K  q  v  V  Z
标点 @  #  *
Ctrl 字符^A ^K ^O ^W ^X

在 Vim 中,几乎全部字符都被用作命令名。尽管如此,有些命令用得极少, 没有理由不去替换它们。这些字符显示在第二张表中。

Vim:可用作宏名的字符
字母 K  q  v  V
标点 @  \
Ctrl 字符^@ ^A ^K ^O ^T ^X

从某种意义上说,宏就是微小的程序。和所有编程工具一样,细节多到你永远 用不完,所以我不会把所有细微之处都讲透。(*) 取而代之,我会给你展示几条 更有用的宏,让你了解一下它们能为你做什么。

* 脚注

如果你学会足够多的技巧并花足够多的时间,你能写出非常复杂的 vi 宏。例如,有人写过宏来解决汉诺塔问题、模拟一台图灵机(两个经典的计算机 科学问题)。如果你喜欢这类东西,你应当知道 Vim 附带了比标准 vi 精密得多的工具。用 Vim,你可以录制、重放和修改宏。你还可以用一个功能齐全 的脚本设施来编写程序。更多信息,请在网上搜索 "vim macros" 和 "vim scripting"。

正如我们本章前面讨论过的,G 命令把光标移到编辑缓冲区的末尾。 1G(转到第 1 行)命令移到缓冲区的开头。在某些版本的 vi 中(以及 Vim),你可以用 gg 来代替 1G。然而,如果你的 vi 版本不支持 gg,有一条简单的命令来取代它就很方便。 考虑下面这个:

:map g 1G

这条命令定义了一个名为 g 的宏,它展开成 1G 命令。现在, 当你想在编辑缓冲区里上下飞奔时,你可以键入 g(小写 "g")跳到开头, 键入 G(大写 "G")跳到末尾。

这里是另一条更复杂的宏。假设你在用一种像 C 或 C++ 这样的语言写程序, 其中注释被 /**/ 包围,例如:

/* This line is a comment */

下面这条宏通过在行首插入 /*、在行尾插入 */,把一条普通 的行变成一条注释行:

:map * I/* ^V<Esc>A */^V<Esc>

我们把这条宏拆开来看。在 :map 之后,你会看到一个 * (星号)字符。这将是宏的名字。

接下来是命令。首先,我们用 I 在行首进入输入模式。然后我们键入 /* 后跟一个空格。到了这一点,我们需要按 <Esc> 键退出输入 模式。要在宏里插入一个 <Esc> 码,我们键入 ^V<Esc>。 (正如我们本章前面讨论过的,^V[Ctrl-V]告诉 vi 下一个键要 按字面意义来取。)

接着我们用 A(append,追加)命令在行尾进入输入模式。然后我们键入 一个空格,后跟 */。要退出输入模式,我们再用一个 <Esc>。

如果你亲自输入上面这条命令,你会看到 <Esc> 码显示为 ^[。 换句话说,你在屏幕上会看到的是:

:map * I/* ^[A */^[

这是因为 <Esc> 码实际上就是 ^[,就像退格是 ^H (见第 7 章)、制表符是 ^I 一样。

某些版本的 vi 允许你把一个宏指派给功能键 <F1> 到 <F10>。为此,你用 # 字符后跟一个数字来指代功能键: 1=F1,2=F2……0=F10。例如,下面这条命令创建了一个指派给 <F1> 键的宏:

:map #1 :set all

任何时候,你都可以通过单独使用 :map 命令来显示你所有宏的列表:

:map

要移除一个宏,使用 :unmap 命令。语法是:

:unmap x

其中 x 是宏的名字。例如:

:unmap g
:unmap #1

初始化文件:.exrc.vimrc
(Initialization Files: .exrc, .vimrc)

vi 或 Vim 启动时,它会在你的主目录中寻找一个初始化文件。 如果这样的文件存在,程序就会读取它并执行它所发现的任何 ex 命令。 这让你能够自动初始化你的工作环境。(主目录在第 23 章讨论,初始化文件在 第 14 章讨论。)

vi 而言,初始化文件名为 .exrc(读作 "dot-E-X-R-C")。 对 Vim 而言,文件名为 .vimrc("dot-vim-R-C")(*)。正如我们在第 14 章讨论过的,开头的 .(点)表明这些是隐藏文件;"rc" 这个后缀代表 "run commands"(运行命令)。

* 脚注

Vim 会先找一个 .vimrc。如果这个文件不存在,它接着会去找一个 .exrc 文件。因此,如果你同时有两个文件,只有 .vimrc 文件会被读取。

创建一个 vi 初始化文件很简单直接:只要插入你希望每次启动 vi 时自动执行的那些 ex 命令。尤其,你应当把你经常使用的所有 :set(选项)、:ab(缩写)和 :map(宏)命令都写进去。 你还可以用 :! 命令来运行 Shell 命令。

vi 读取初始化文件时,以 "(双引号)字符开头的行会被忽略, 这意味着你可以用这样的行来做注释。类似地,行首的空格和制表符也会被忽略, 这让你能够为了可读性给行加缩进。(*) 最后,vi 假定它所读到的一切都是 一条 ex 命令,所以你不必用冒号来开始任何命令。

* 脚注

某些版本的 vi(以及 Vim)也会忽略初始化文件中的空行,这让你能 把你的文件弄得更易读。如果你的 vi 版本不允许空行而你又用了空行, 你会看到一条含糊的错误消息,比如:"Error detected in .exrc."

为了说明这些想法,图 22-12 包含了一份你可以在 vi 或 Vim 中使用的 初始化文件示例。

图 22-12:vi/Vim 初始化文件示例

当你启动 vi 或 Vim 时,程序会在你的主目录中寻找一个初始化文件。 这里有一份示例文件,你可以把它当作模板来创建你自己的初始化文件。详情见正文。

" =================================
" Sample vi/Vim initialization file
" =================================
"
" 1. Options
    set autoindent
    set compatible
    set ignorecase
    set showmatch
    set showmode
    set wrapmargin=6
"
" 2. Abbreviations
    ab eg exceptionally gifted
    ab h Harley
"
" 3. Macros
    map K deep
    map X dd
    map g 1G
    map #5 {!}fmt^M
"
" 4. Shell commands
    !date; sleep 2

第 1 部分按如下方式设置选项:

autoindent:(输入模式)当使用 wrapmargin 进行自动缩进时, 让缩进与上一行或下一行对齐。

compatible:(仅 Vim)强制 Vim 以 vi 兼容模式运行。如果即使 你用了 -C 选项,Vim 仍不以 vi 模式运行,就用这个。

exrc:启动时,在当前目录中寻找第二个初始化文件(下一节会解释)。

ignorecase:搜索时,忽略大写与小写之间的差别(非常有用)。

showmatch:(输入模式)当你键入一个右圆括号、方括号或花括号时, 高亮显示与之配对的那个左圆括号、方括号或花括号。

showmode:当你在输入模式时显示一个提醒。

wrapmargin:指定文本距离行尾多近时应触发自动缩进。

第 2 部分创建缩写。这里是为难拼的词、HTML 标签、编程关键字等指定简写的 好地方。

第 3 部分定义宏。正如我们本章前面讨论过的,宏可以很复杂(尤其是在 Vim 中)。 这里的示例宏大多简单明了。不过,我确实想就两点作个说明。

第一,如果你的 vi 版本支持 gg 命令,你就不需要 g 宏。

第二,使用 <F5> 功能键(#5)的那条宏以一个 ^M 字符—— 回车码——结尾,用来模拟你按 <Return> 键。注意这是一个单独的控制字符, 不是两个分开的字符。当你把这一行键入初始化文件时,你必须按 ^V<Return> 或 ^V^M 来插入一个真正的 ^M

第 4 部分包含 Shell 命令。我用了一整行命令来向你展示它是怎么工作的。 在这个情形里,我用 date 命令(第 8 章)来显示时间和日期。 sleep 命令会暂停指定的秒数(这里是 2 秒)。

如果你的初始化文件包含一条错误的命令,vi 会显示一条错误消息并在那 一点停止执行该文件。虽然 vi 会正常启动,但文件里剩下的初始化命令将 不会被处理。Vim 更宽容:它会显示一条错误消息,请你按 <Enter>,然后继续 执行文件里的下一条命令。

使用两个初始化文件
(Using Two Initialization Files)

在那些你需要额外定制的情形里,使用一个额外的初始化文件是可行的。在我能 解释它如何工作之前,我需要简要提几个我们将在第 24 章讨论的概念。

当一个用户 ID 被创建时,它会被赋予一个属于自己的目录,叫做"主目录"。在你的 主目录内,你可以根据需要创建任意多个子目录。每次你登录时,你都在你的主目录 里开始工作。然而,工作时从一个目录改到另一个目录是很容易的。任何时候,你 当前正在使用的目录都被称为你的"工作目录"。

当你运行 vi 或 Vim 时,它从执行主目录里的初始化文件开始。接着它会 检查 exrc 选项的状态。如果这个选项被打开了,程序就会在你的当前目录 中寻找要执行的第二个初始化文件(假定你的当前目录与你的主目录不同)。 用这种方式,你可以把你的文件组织到子目录中,每个子目录都有它自己的初始化文件。

例如,假设你目前正在做三个项目:一篇散文、一个程序和一个网页。这些项目的 文件保存在三个分开的、名为 essayprogramwebpage 的目录里。在每个目录里,你创建一份定制的 .exrc.vimrc 文件,其中包含你在编辑那个特定项目的文件时想用的选项、缩写和宏。

你正在 program 目录里工作,你想编辑一个名为 test.c 的文件。 你输入命令:

vi test.c

vi 一启动,就会在你的主目录中寻找一个初始化文件。一旦这个文件里的 命令被执行,vi 就检查 exrc 选项。如果它是打开的,vi 会在 program 目录(你的当前目录)中寻找第二个初始化文件。用这种方式, 你就能以一种适合编辑程序的方式来定制你的工作环境。

学习使用 Vim
(Learning to Use Vim)

在本章开头,我们谈到了 Vim,一个复杂的、与 vi 向后兼容的文本编辑器。 Vim 由荷兰程序员 Bram Moolenaar 于 1988 年创建,作为一个 vi 克隆的 "改进"版本。从那时起,Vim 被极大地扩展,拥有了数百项新功能。与此同时, Vim 变得如此流行,以至于在许多 Unix 和 Linux 系统上,它取代了 vi 的位置。虽然你可以让 Vim 以 "vi 兼容"模式运行,但它远不止是 vi 的改进版本。Vim 本身就是一个非常精密的编辑器,与 vi 显著不同。

事实是,Vim 复杂到任何人都无法把它教给你。你必须自己学它。然而,当你起步时, 你会发现一个迫在眉睫的问题:Vim 的文档一点也不适合初学者。解决办法是先从学 vi 开始。一旦你理解了 vi,你就会有某种上下文背景。然后你可以 通过一次给自己的知识添加一点点来自学 Vim。这就是本章我们专注于 vi 而非 Vim 的一个原因。(另一个原因是 vi 无处不在,而 Vim 在许多 Unix 系统上并不具备。)

所以,如果你想学 Vim,这里是我的建议。先读本章并练习,直到你觉得自己掌握了 vi。那至少该花你一个月或两个月。在这段时间里,你以 vi 兼容模式 运行 Vim。(如何这样做的说明在本章前面可以找到。)之后,你可以关掉兼容模式, 开始把 Vim 当作它本身来使用。

我猜你已经用过许多程序——尤其是图形用户界面的程序——你在用的时候就能当场 上手。Vim 不同。Vim 是你必须自学的一个东西,而要自学,你一定要读文档。(*) 先从 Shell 提示符下运行下面这条命令开始:

vimtutor

* 脚注

如果你请另一个人来教你 Vim,结果只会是你一头雾水,而他或她会沮丧透顶。 别说我没提醒过你。

这会显示一个教程,概述了基本命令(其中大多数你已经从本章学过了)。当你结束时, 键入 :q

接下来,启动 Vim,看看在线帮助,依次输入下面每一条命令。(同样,你可以键入 :q 退出。)

:help
:help user-manual
:help differences

如果你觉得以这种方式读文档很乏味(就像我一样),那就在线读。你可以在 www.vim.org 找到你需要的东西。(四处找找,直到你找到 Vim 的 "User Manual" (用户手册)。)

请不要让我的这些话让你泄气。Vim 是一个了不起的程序,如果你有这个兴致,你绝对 应该学它。为了激励你,图 22-13 汇总了 Vim 所提供的最重要的增强功能。当你以你对 vi 的知识为基础来学 Vim 时,我的建议是按照你在图中看到的顺序去学习各项 功能的用法。

图 22-13
Vim:相对标准 vi 的增强

Vim 是一个高度精密的文本编辑器,相对标准 vi 有大量增强。这里是对 其中最重要的一些功能的汇总。学 Vim 最好的方式,是先从掌握 vi 开始。 然后按我列出的顺序自学如何使用这些增强功能。

总有个中遗憾
(It's Always Something)

作为本章的结尾,我想给你讲一个真实的故事,它体现了美国诗人 John Greenleaf Whittier(1807-1892)在他的诗作 Maud Muller 中所抒发的那种怅惘,他写道:

For of all sad words of tongue or pen,
The saddest are these: "It might have been!"

正如你在图 22-13 中所看到的,Vim 所提供的重要增强之一是"分屏",即把屏幕分成 水平或垂直窗口的能力。这是一个强大的工具,因为它让你能够同时查看多于一个文件。

有趣的是,早在 1978 年,在加州大学伯克利分校,Bill Joy(vi 的创造者)正 打算把那项确切的功能放进该程序的一个早期版本。以下是用他自己的话讲的故事,出自 1984 年 Unix Review 杂志的一篇采访:

"实际发生的情况是,我正着手给 vi 添加多窗口功能,就在这时我们安装 了我们的 VAX[计算机],那大概是 78 年 12 月。我们没有任何备份,而磁带机坏了。 即使没法做备份,我还是继续工作——然后源代码被弄坏了,而我手里没有完整的清单。

"我几乎已经重写了用于窗口的全部显示代码,就在那时我放弃了。在那之后,我 退回到上一个版本,只是把代码写了文档,做完了手册,就把它收尾了。要是那次弄坏 没有发生,vi 就会有多窗口。而且我本来也许会加入某种可编程性,但我不 知道。"

恰逢其时,美国哲学家 Roseanne Roseannadanna 正在参观伯克利的 Unix 实验室 (图 22-14)。当 Joy 把发生的事告诉她时,她评论道:"嗯,Bill,这就像我老爹过去 常跟我说的那样——'Roseanne Roseannadanna,要不是这桩事,就是那桩事。'"

图 22-14:Bill Joy 与 Roseanne Roseannadanna

这张未经修饰的照片展示了 Roseanne Roseannadanna 在加州大学伯克利分校 Unix 实验室拜访 vi 的创造者 Bill Joy。

练习
(Exercises)

复习问题 #1:

当你想要做下面这些事时,你如何启动 vi:

• 编辑一个名为 document 的文件
• 编辑一个全新的文件
• 以只读模式打开一个名为 document 的文件

你如何以 vi 兼容模式启动 Vim?

复习问题 #2:

如果你已经保存了你的工作,你如何退出 vi?

你如何保存你的工作然后再退出?

你如何不保存就退出?

复习问题 #3:

当你用 vi 工作时,你的数据保存在一个存储区域中。这个存储区域 叫什么?

vi 编辑器在两种主要模式下工作:命令模式和输入模式。描述每一种模式。

你如何从命令模式切换到插入模式?

你如何从插入模式切换到命令模式?

复习问题 #4:

为把光标移到下列每一个目的地,指出最佳的命令。只要可能,就用字母键。

• 向左、向下、向上、向右各一个位置
• 当前行的开头
• 当前行的末尾
• 上一行的开头
• 下一行的开头
• 向前一个词
• 向后一个词
• 向前到下一个句子
• 向后到上一个句子
• 向前到下一个段落
• 向后到上一个段落
• 屏幕的顶行
• 屏幕的中间行
• 屏幕的底行
• 编辑缓冲区的开头
• 编辑缓冲区的末尾
• 向下整整一屏
• 向上整整一屏
• 向下半屏
• 向上半屏

复习问题 #5:

在命令模式内,你如何:

• 撤销上一条修改编辑缓冲区的命令。
• 把当前行恢复成你移到它上面时的样子。
• 重复上一条修改编辑缓冲区的命令。

运用你的知识 #1:

启动 vi 并创建一个全新的空文件,名为 temp。把以下几行 插入文件:

one 1
two 2
three 3
four 4
five 5

用一条 vi 命令保存你的工作并退出。

运用你的知识 #2:

启动 vi 来编辑上一题中的文件 temp

仅使用 vi 命令:
• 把第 2 到 4 行移到第 5 行之后。
• 撤销这次移动。

仅使用 vi 命令:
• 把第 2 到 4 行复制到编辑缓冲区的顶部。
• 撤销这个更改。

到了这一步,编辑缓冲区看起来应当和你刚开始时一样。
• 不保存就退出

运用你的知识 #3:

启动 vi 来编辑上一题中的文件 temp

尽可能使用 ex 命令:
• 把第 2 到 4 行移到第 5 行之后。
• 撤销这个更改。

尽可能使用 ex 命令:
• 把第 2 到 4 行复制到编辑缓冲区的顶部
• 撤销这个更改
• 到了这一步,编辑缓冲区看起来应当和你刚开始时一样
• 不保存就退出

把你在练习 #2 中用到的 vi 命令与你在练习 #3 中用到的 ex 命令作个比较。ex 命令有什么优势?

运用你的知识 #4:

启动 vi 来编辑练习 #1 中的文件 temp:

• 在编辑缓冲区的底部插入日期和时间

光标在哪里?为什么?

不先移动光标:

• 用一条命令把编辑缓冲区中所有的行按字母逆序排序

• 不保存就退出

进一步思考 #1:

一旦你对 vi 编辑器运用自如,你会发现它快捷、强大、易于使用。 然而,vi 是一个非常复杂的程序,要精通它需要下很大功夫。向后兼容的 替代者 Vim 更强大、更复杂,甚至更难学。考虑到 vi 已有超过 30 年的 历史且如此难学,你认为它为什么在 Unix 社区里仍然如此流行?

你是否预见这样一个未来:复杂的任务将完全由易于使用的工具来完成,还是说 像 vi 这样的程序总会有其存在的需要?

进一步思考 #2:

大体而言,vi 有两类不同的命令:面向屏幕的 vi 命令和面向行的 ex 命令。这两类命令彼此完全不同,而且确实是为不同类型的硬件开发的。 尽管如此,它们却能很好地结合起来,创造一个强大的编辑环境。这是为什么?

这关于"我们应当为聪明人设计什么样的工具"这件事,告诉了你什么?