vi 编辑器指南
1. 概述
在数字时代完成工作在很大程度上取决于我们处理文本数据的速度和效率。
在本教程中,我们将采用老式方法来了解传统 vi 编辑器作为一种简约的编辑器强大的文本编辑工具。
2. 传统vi
我们将将探索范围限制在传统 vi 编辑器。考虑到这一点,让我们首先了解 vi 与 ex 行编辑器的连接。
2.1.设置
长期以来,vi 的功能集一直是单一 Unix 规范的一部分,使其成为大多数 Linux 中的默认编辑器。然而,目前,大多数操作系统通过其克隆 Vim (Vi IMproved) 提供 vi 支持:
ls -l $(which vi)
lrwxr-xr-x 1 root wheel 3 Jan 9 05:03 /usr/bin/vi -> vim
因此,每当我们调用 vi 时,我们实际上是在调用 vim。
为了确保在 vi 中工作的所有功能在 Vim 中继续工作,我们可以选择使用 Vim 的兼容模式(-C):
$ vim -C
但是,作为一个增强的克隆,Vim 自然提供了比传统 vi 编辑器更广泛的功能集。而且,兼容模式并不限制我们在 Vim 中使用这些互斥的功能。
现在,从功能角度来看,这一切都很公平而且很好。但是,我们最终可能会使用非 vi 功能并假设它是 vi 功能。
为了避免这种混乱,并明确 vi 编辑器支持的功能,我们将利用 一个预装了 vi 的即用型虚拟 Docker 环境。
那么,让我们运行我们的虚拟环境并开始吧:
$ docker run -it tapankavasthi/vi
[email protected]:/tmp/vi/work#
2.2. vi 是ex
在我们的虚拟容器中,让我们使用 file 命令检查 vi 可执行二进制文件:
# file $(which vi)
/usr/local/bin/vi: symbolic link to ex
我们可以看到,vi可执行文件是ex二进制文件的符号链接。因此,很明显,为 vi 提供支持的底层程序是 ex。
2.3. ex模式
让我们创建一个示例 hello.txt 文件并使用 ex 命令打开它:
# printf "Line-1\nLine-2\n" > list.txt
# /usr/local/bin/ex list.txt
令人惊讶的是,一旦打开文件,我们就看不到文件的内容。另一方面,我们看到一个冒号 (:) 命令提示符:
"hello.txt" 2 lines, 14 characters
:
因此,这是 ex 的默认模式,我们希望在冒号提示符下运行 ex 命令。那么,让我们尝试一下以 % 符号为前缀的 p(打印)命令:
"hello.txt" 1 line, 14 characters
:%p
Line-1
Line-2
:
2.4. ex的视觉模式
如果我们看一下 ex 的命令行用法,那么我们可以发现一个 -v (视觉)选项:
Usage: ex [- | -s] [-l] [-L] [-R] [-r [file]] [-t tag]
[-v] [-V] [-w size] [+cmd | -c cmd] file...
让我们使用 q(退出)命令退出最后打开的文件,并使用 -v 选项重新打开它:
$ /usr/local/bin/ex -v list.txt
有趣的是,我们现在只要打开文件就可以看到文件的内容。好吧,它正确地称为视觉模式:
Line-1
Line-2
~
~
~
"list.txt" 2 lines, 14 characters
或者,我们可以直接使用 vi 命令调用 ex 的视觉模式:
$ vi list.txt
因此,我们可以说 vi 是 ex -v。
3. vi 作为模态编辑器
当我们使用vi命令打开文件时,我们进入ex的可视模式。但是,从 vi 的角度来看,这是默认模式。
我们可以在此模式下使用 ex 命令的子集来进行编辑。所以,vi 俗称命令模式。 vi 还支持插入模式,其中大部分击键都会转化为在文件中键入文本。
而且,我们可以通过分别按 Esc 或键入 i 轻松地在命令模式和插入模式之间切换。
最后,vi 还提供了对类似 ex 的模式的轻松访问,我们可以在冒号提示符下输入命令,而不会丢失视觉上下文。
现在,当涉及到切换这些模式时,最好通过采用视觉方法来理解这一点:
作为一个快速练习,让我们使用这个循环流程来弄清楚如何从 ex 的冒号提示符转到插入模式。
4. 视觉模式语言
在每种模式下,vi 都理解一组特定的指令。为了在视觉模式下有效地工作,让我们从学习它的语言概念开始。
4.1.语法基础知识
一般来说,我们需要按照特定的顺序向 vi 提供编辑指令:
[Modifier][Operator][Operand]
为了澄清起见,让我们看一个删除三个字符的简单指令:
因此,字符、单词和行形式的文本对象用作操作数。另一方面,运算符执行文本处理,例如更改 (c)、删除 (d)、复制 (y)、替换 (r),等等。最后,诸如数字计数之类的修饰符可以修改运算符的原始行为。
从本质上讲,文本编辑就好比解数学公式,我们可以得到一个问题的多种解法。但是,在这种情况下,我们应该努力做到最少的击键次数。而且,这就是为什么在使用 vi 时单次击键非常常见的原因之一。
4.2.通用操作数
当我们处理文本时,vi 理解三大类操作数:即字符、单词和行。
听起来很简单吧?但是,细节决定成败,尤其是对于词语的概念而言。
让我们先来了解一下字符的概念。字符操作数是通过参考点来识别的,例如光标的当前位置或行的起始位置:
- 对于当前行,0和$表示开始和结束
- 对于光标位置,h和l分别表示左侧和右侧的字符
- ^ 和 _,都标识第一个非空白字符
- N| 表示第 N 列的字符,缺少 N 值将解释为 1
现在,让我们阐明用于单词的 vi 定义:
- 首先是词汇 (W),这是一个非空格字符的连续序列,后面总是跟着一个空格[ ^I]* 或行结束符 ($)
- 第二个是逻辑词(w),它总是以非空白字符开头,后跟一系列[a-zA-Z0-9]* >
此外,让我们了解一些从单词操作数派生的字符标识符:
最后,我们还看一下一些特定于行的操作数:
4.3.常用运算符
由于操作数带有关联字符的明确位置标识,因此当我们没有显式指定运算符时,光标移动是应用它们的默认运算符。因此,当我们处于命令模式时,按下操作数对应的按键会将光标移动到操作数标识的位置。
然而,还有一些其他运算符要求我们明确表达我们的意图,以防止意外修改文本。让我们看看其中的一些:
当然,操作符列表一开始可能会让我们不知所措。但是,有了语法结构的坚实基础,我们就能直观地记住这些运算符了。下面我们就来看看它们的实际应用:
到目前为止,一切都很好。现在,让我们看一下两个让人感觉我们处于插入模式的运算符:
现在,让我们看看如何使用 c (运算符)和 w (操作数)的组合来更改单词:
我们可以看到,它带我们进入插入模式的范围版本,其中$符号表示替换文本的预期软边界。并且,确认更改并返回正常的视觉模式,我们必须按Esc键。
4.4.操作范围
对于 dw、d/<search_word> 等编辑操作,我们是从左向右逐个字符遍历的。因此,我们的操作范围是字符级。
然而,当涉及dj、ck或yj、等编辑操作时,我们就只能在行级别上操作了。
在这种情况下,位于当前光标位置和 j 或 k 目标字符位置之间的文本跨越两行。因此,对于这些操作,结果是删除、更改或复制两行:
4.5.重复
文本编辑常常涉及重复性工作,比如对多个字符、单词或行进行相同的操作。而 vi 提供的两种功能可以让我们轻松完成这些工作:
- 点 (.)运算符重新运行上次编辑操作
- 命令之前的数字限定符会为许多后续操作数重复该命令
假设我们有一些代码需要正确的制表符缩进。当然,>> 和 << 运算符在这里会派上用场。
因此,如果我们必须通过一个制表符缩进多个连续行,那么我们可以在 >> 之前使用数字量词:
现在,我们有另一种情况,我们希望同一行有多个制表符缩进。因此,这里我们可以使用 >> 或 << 一次,后跟点运算符来重复之前的命令执行:
5. 上下文管理
为了方便导航和上下文管理,vi 提供了标记和寄存器。让我们详细了解一下它们。
5.1.标记
文本编辑涉及从一行到另一行的大量光标移动。不幸的是,在这样做时,我们可能会忘记当前的上下文。因此,为了解决这个问题,vi 让我们标记当前光标位置以供以后使用。
要使用标记,我们首先熟悉与它们相关的重要键绑定:
- mλ 创建一个标记 - 其中 λ 是 a 到 z 之间的任何小写字母
- ‘λ 标识标记行中的第一个非空白字符
- `λ 在一行上进行标记的准确位置
- ‘` 标识光标前一行位置上的第一个非空白字符
- `’ 标识光标在上一行的准确位置
假设我们有一个包含两个部分的文件,即诗体及其参考文献。让我们看看如何用 mp 和 mr 标记这两个部分,在它们之间进行导航:
现在,如果在某个时候,我们不需要文件中的引用列表,那么我们可以在编辑操作中使用此标记信息,G$d’r:
5.2.寄存器
在大多数文本编辑环境中,我们都有一个剪贴板的概念,我们可以在其中复制一些内容以供以后使用。但是,使用 vi,我们可以获得多个这样的占位符桶,称为寄存器。
因此,我们不能将寄存器与变量混淆。虽然变量名通常是用户定义的,但有一组预定义的寄存器,我们无法重命名它们。所有寄存器名称均以 “ 字符开头,后跟单个字符。
当然,寄存器支持来回文本传输操作,例如 yank (y)、delete (d)、cut (x) 和 paste (p):
<register-name><text-transfer-operator>
因此,“5yy操作会将行复制到数字寄存器“5中,当需要时,我们可以执行类似“5p或“5P。
尽管这对于简单的用例来说效果很好,但是,我们应该避免显式覆盖数字寄存器中的内容“0-9。这是因为 vi 使用数字寄存器作为保存最近十次文本传输操作中的文本的方法:
Text Transfer -> "0 -> "1 -> "2 -> "3 -> "4 -> "5 -> "6 -> "7 -> "8 -> "9 -> Lost
即使我们显式使用寄存器,也会发生这种文本传输序列。因此,当我们显式写入数字寄存器时,vi 总是有机会干扰我们的工作流程。
在 vi 寄存器池中,“a-z和“A-Z被称为命名寄存器。关于这些寄存器需要注意的重要一点是,每个小写命名寄存器及其相应的大写命名寄存器都指向相同的存储位置。但是,在将文本移入其中时,它们表现出不同的行为:
- 小写命名寄存器“a–“z用移入其中的新内容替换旧内容
- 大写命名寄存器 “A –“Z 将新内容附加到带有换行符的先前内容
现在,假设我们正在处理一个包含 HTTP 和 HTTPS URI 混合的文件。我们的目标是将他们隔离。有趣的是,vi 中的删除操作更像是剪切操作,并且可以从寄存器中保存和检索数据。因此,让我们制定一个策略来通过删除操作和寄存器来解决我们的用例:
- 寄存器“S和“U可分别用于HTTPS和HTTP URI的列表
- <register>dd 操作可用于将 URI 信息临时转移到寄存器中。
- 要擦除寄存器中的旧数据,第一次删除可以使用小写的named-register
- 标记 s 和 u 将用于为 HTTPS 和 HTTP URI 隔离列表的位置上下文添加书签
最后,让我们看看我们的计划是如何实施的:
6.ex模式
因为 ex 最初是一个行编辑器实用程序。因此,当我们的编辑任务涉及行级操作时,ex 模式可以证明非常有用。让我们准备好在冒号提示符下执行一些命令。
6.1.地址基础知识
在 ex 模式下,我们需要提供要执行特定命令的行地址。因此,大多数 ex 的编辑命令都需要 address_range 前缀:
:<address_range> <editing-command>
此外,我们可以用多种格式指定地址范围:
- 数字行号、当前行 (.) 或最后一行 ($)
- 相对于当前行的相对地址,用方向 (+,–) 和正负数表示,用于计算行步数
- 作为正则表达式 /regex/
- 上述三种类型中任意一种的两个地址值的逗号分隔值
- 作为由 % 符号表示的全范围线
我们必须注意,如果我们不指定地址,那么默认情况下,该操作将仅针对当前行(.)执行。
现在我们已经对地址概念有了理论上的理解,让我们使用 p (print) 命令看看实际情况:
我们必须注意,每次地址评估后当前行都会发生变化。因此,无需 p (print) 命令即可使用此行为来转到地址范围的最后一行:
:<address-range>
6.2.常用编辑命令
虽然 p(命令)在尝试理解地址概念时很方便,但当我们有多行可视屏幕时,我们并不真正需要它。因此,让我们花一些时间来实践一下 ex 模式下一些常用的编辑命令。
首先,让我们看一些简单的命令,例如删除 (d)、移动 (m):
- [address_range] d [register],可用于将行从地址范围移动到寄存器
- [address_range] y [register],可用于将地址范围中的行拉至寄存器
- [address_range] pu [register],可用于将寄存器的内容放在指定地址范围之后
- [address_range] j,可用于连接落在指定地址的行
- [address_range] m <target_address>,可用于在 target_address后放置一系列行
- [address_range] co <target_address>,可用于复制 target_address后的一系列行
接下来,让我们看看替换命令(s),因为它可能是最常用的ex命令之一:
:[address_range] s/pattern/replacement/[[g|count][cp]]
顾名思义,s 命令在给定地址范围中搜索模式,然后将其替换为替换字符串。默认情况下,每行仅进行一次替换,但我们可以更改此行为:
- 使用g(全局)标志,每行的所有匹配项都会被替换
- count 值限制每行的替换数量
- c(确认)标志提示在进行替换之前进行确认
- 使用 p (print) 标志,打印最后一次成功替换的行
现在,假设我们在文件中有一个项目名称列表,每行一个。而且,我们的目标是将其转换为单行上的逗号分隔列表。让我们看看如何使用 substitution (s) 和 join (j) 命令来做到这一点:
我们必须注意,按键在不同模式下可以执行不同的操作,例如j在命令模式下将光标向下移动一行,但在ex模式下执行连接。
6.3.重复
vi 如此强大的功能之一是能够轻松重复操作。并且,要在 ex– 模式下重复执行编辑操作,我们可以使用 g (global) 命令:
:[address_range]g/pattern/cmd
默认情况下,ex 模式下的任何编辑命令都会在整个地址范围上执行一次。但是,当与global (g)命令结合使用时,命令将为地址范围内与模式匹配的每一行执行一次。
让我们重新审视 URI 隔离问题,我们必须隔离 HTTP 和 HTTPS URI 的混合列表,但这一次,我们将在 ex-mode 中解决它:
重复命令类别中的另一个瑰宝是&命令,它让我们重复最后一个替换命令。
因此,让我们用它来确定仅由“(”和“)”字符组成的给定表达式是否满足平衡字符串的标准。嗯,对于平衡字符串,所有左括号后面都会跟着相应的右括号。
现在,我们可以通过重复将每对匹配括号()减少为空字符串来检查这一点。此外,当替换命令无法匹配模式时,我们将停止。并且,如果我们留下空行,则原始字符串是平衡的:
6.4.缓冲区管理
到目前为止,我们已经对文件执行了各种编辑操作。尽管我们验证了更改在屏幕上可见,但猜猜看,我们的更改并未永久写入磁盘上的文件中。这是因为我们使用的是 buffer,它实际上是文件内容的副本,驻留在易失性内存中。就此而言,甚至寄存器也是缓冲区的一类。
要将更改永久写入磁盘上的文件,我们可以使用 w (写入)或 w!(强制写入) 命令:
:w[!] [filename]
我们必须注意,我们可以通过提供与当前打开的文件不同的文件名来将更改写入新文件。并且,稍后,如果我们想退出,则可以使用 q (退出)或 q! (退出而不写入)命令。然而,一旦我们退出 vi,我们也会丢失其寄存器中的所有可用数据。
让我们选择包含 HTTP 和 HTTPS URI 隔离列表的文件。现在,我们的要求是,我们应该将这些隔离列表放在两个不同的文件中,即 http_urls.txt 和 https_urls.txt。
要使用多个缓冲区,我们将使用 edit (e) 命令,以便我们可以借助 vi 的命名寄存器跨文件传输文本:
:e [file1 file2 ...]
因此,vi 要求我们在切换到另一个文件进行编辑之前保存当前文件。然而,有时,我们可能不想保存更改;在这种情况下,我们可以使用e!恢复到文件缓冲区的最后写入版本。
现在,让我们看看如何使用 edit (e) 命令将 URI 分成两个不同的文件:
7. .exrc 环境文件
我们每个人都有独特的工作风格,因此,我们当然喜欢编辑器中的一些个性化设置。对于 vi,执行此操作的方法是通过 .exrc 文件。
让我们探讨一下如何调整 vi 编辑会话。
7.1. 设置命令
顾名思义,set命令帮助我们设置控制编辑器功能行为的标志和变量的值。嗯,我们中的一些人通常喜欢在左侧看到行号。那么,让我们看看如何使行号自动出现在每个会话中:
因此,我们可以根据需要使用更多此类标志。为此,我们可以在ex模式下查看set命令可用的所有选项:
:set all
7.2.缩写
为了节省一些击键次数,vi 让我们在 .exrc 文件中定义缩写:
ab <short-phrase> <expanded-text>
设置缩写后,我们可以在插入模式下使用它,方法是在输入缩写词后按空格键。但是,它仅在用作整个单词时才会展开,而不是作为单词的一部分使用。另一个词。
现在,让我们看看如何定义和使用国家/地区代码的缩写:
稍后,如果我们想要删除缩写,那么我们可以使用unab命令:
:unab <phrase>
7.3.键映射
vi 的另一个有用功能是能够更快地完成操作,即能够使用 map 命令定义自定义键盘快捷键:
map <NewKeySequence> <TargetKeySequence>
假设我们的工作需要处理大量以逗号分隔的数据。那么,如果我们能够通过一次击键从列数据中生成逗号分隔值,岂不是很好?好吧,让我们继续将此行为分配给 V 键:
map V :1,$-1s/$/,/^M :%j^M
最后,让我们看看实际效果:
我们必须注意,^M代表回车键的文本表示形式,需要以<Ctrl-v> <CR>键的组合形式写入 .exrc 文件。
八、结论
在本教程中,我们通过解决一些文本编辑问题为 vi 基础知识奠定了坚实的基础。因此,使用 vi 的最大好处是总是有多种方法可以解决给定的问题。而且,用我们自己的创造力发现新的 vi 技巧非常有趣。