在编辑文件的时候,你会发现有时候你在反复地做一些相同的动作。如果你仅做一次,并在需要的时候调用这些动作岂不是会更好吗。通过 Vim 的宏命令,你可以将一些动作记录到 Vim 寄存器。
在本章中,你将会学习到如何通过宏命令自动完成一些普通的任务(另外,看你的文件在自动编辑是一件很酷的事情)。
宏命令的基本语法如下:
qa 开始记录动作到寄存器 a
q (while recording) 停止记录
你可以使用小写字母 (a-z)去存储宏命令。并通过如下的命令去调用:
@a Execute macro from register a
@@ Execute the last executed macros
假设你有如下的文本,你打算将每一行中的所有字母都变为大写。
hello
vim
macros
are
awesome
将你的光标移动到 “hello” 栏的行首,并执行:
qa0gU$jq
上面命令的分解如下:
qa
开始记录一个宏定义并存储在 a 寄存器。0
移动到行首。gU$
将从光标到行尾的字母变为大写。j
移动到下一行。q
停止记录。
调用 @a
去执行该宏命令。就像其他的宏命令一样,你也可以为该命令加一个计数。例如,你可以通过 3@a
去执行 a
命令3次。你也可以执行 3@@
去执行上一次执行过的宏命令3次。
在执行遇到错误的时候,宏命令会自动停止。假如你有如下文本:
a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut
你想将每一行的第一个词变为大写,你可以使用如下的宏命令:
qa0W~jq
上面命令的分解如下:
qa
开始记录一个宏定义并存储在 a 寄存器。0
移动到行首。W
移动到下一个单词。~
将光标选中的单词变为大写。j
移动到下一行。q
停止记录。
我喜欢对宏命令进行很多次的调用,所以我通常使用 99@a
命令去执行该宏命令99次。当 Vim 在最后一行执行 j
命令的时候,会发现已经没有下一行可以继续,遇到执行的错误,因此宏命令会停止。
实际上,遇到错误自动停止运行是一个很好的特性。否则,Vim 会继续执行该命令99次,尽管它已经执行到最后一行了。
在正常模式执行 @a
并不是宏命令调用的唯一方式。你也可以在命令行执行 :normal @a
。:normal
会将任何用户添加的参数作为命令去执行。例如添加 @a
,和在 normal mode 执行 @a
的效果是一样的。
:normal
命令也支持范围参数。你可以在选择的范围内去执行宏命令。如果你只想在第二行和第三行执行宏命令,你可以执行 :2,3 normal @a
。我会在后续的章节中介绍更多关于在命令行中执行的命令。
假如你有很多的 .txt
文件,每一个文件包含不同的内容。并且你只想将包含有 “donut” 单词的行的第一个单词变为大写。那么,该如何在很多文件中特定的行执行执行变该操作呢?
第一个文件:
# savory.txt
a. cheddar jalapeno donut
b. mac n cheese donut
c. fried dumpling
第二个文件:
# sweet.txt
a. chocolate donut
b. chocolate pancake
c. powdered sugar donut
第三个文件:
# plain.txt
a. wheat bread
b. plain donut
你可以这么做:
:args *.txt
查找当前目录下的所有.txt
文件。:argdo g/donut/normal @a
在所有:args
中包含的文件里执行一个全局命令g/donut/normal @a
。:argdo update
在所有:args
中包含的文件里执行update
命令会将修改后的内容保存下来。
如果你对全局命令 :g/donut/normal @a
不是很了解的话,该命令会在包含有 /donut/
中的所有行执行normal @a
命令。我会在后面的章节中介绍全局命令。
你可以递归地执行宏命令,通过在记录宏命令时调用相同的宏来实现。假如你有如下文本,你希望改变第一个单词的大小写:
a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut
如下命令会递归地执行:
qaqqa0W~j@aq
上面命令的分解如下:
qaq
记录一个空白的宏命令到 “a” 。把宏命令记录在一个空白的命令中是必须的,因为你不会想将该命令包含有任何其他的东西。qa
开始录入宏命令到寄存器 “a”。0
移动到行首。W
移动到下一个单词。~
改变光标选中的单词的大小写。j
移动到下一行。@a
执行宏命令 “a”。当你记录该宏命令时,@a
应该是空白的,因为你刚刚调用了qaq
。q
停止记录。
现在,让我们调用 @a
来查看 Vim 如何递归的调用该宏命令。
宏命令是如何知道何时停止呢?当宏执行到最后一行并尝试 j
命令时,发现已经没有下一行了,就会停止执行。
如果你想在一个已经录制好的宏定义中添加更多的操作,与其重新录入它,不如选择修改它。在寄存器一章中,你学习了如何使用一个已知寄存器的大写字母来添加一个新的寄存器。为了在寄存器“a”中添加更多的操作,你可以使用“A”。假设你不仅希望将第一个单词变为大写,也希望在每一行末尾添加一个句点。
假设当前寄存器“a”中有如下的命令:
0W~
你可以这样做:
qAA.<esc>q
分解如下:
qA
开始在寄存器 “A” 中记录宏命令。A.<esc>
在行的末尾(A
)假如一个句点,并且退出插入模式。q
停止记录宏命令。
现在,当你执行 @a
时,它会跳到行的第一个字符(0
),跳到下一个单词(W
),改变光标选中的字母的大小写(~
),移动到最后一行并且转到插入模式(A
),写入一个句点(.
),退出插入模式(<esc>
)。
在已存在的宏定义的末尾添加新的动作是一个很好的功能,但假如你希望在一个宏命令的中间添加动作该怎么做呢?本节,我会向你展示如何修改一个宏。
假设,在改变第一个单词的大小写和在末尾加入一个句点之间,你想要在单词 “donut” 之前加入 “deep fried”(因为唯一比甜甜圈好的东西就是炸甜甜圈)。
我会重新使用上一节使用过的文本:
a. chocolate donut
b. mochi donut
c. powdered sugar donut
d. plain donut
首先,让我们通过 :put a
调用一个已经录制好的宏命令(假设你已经有了上一节中使用过的宏命令):
0W~A.^[
^[
是什么意思呢?不记得了吗,你之前执行过 0W~A.<esc>
。 ^[
是 Vim 的内部指令,表示 <esc>
。通过这些指定的键值组合,Vim 知道这些是内部代码的一些替代。一些常见的内部指令具有类似的替代,例如 <esc>
,<backspace>
,<enter>
。还有一些其他的键值组合,但这不是本章的内容。
回到宏命令,在改变大小写之后的键后面(~
),让我们添加($
)来移动光标到行末,回退一个单词(b
),进入插入模式(i
),输入“deep fried ”(别忽略“fried ” 后面的这个空格),之后退出插入模式(<esc>
)。
完整的命令如下:
0W~$bideep fried <esc>A.^[
这里有一个问题,Vim 不能理解 <esc>
。所以你需要将其替换为内部代码的形式。在插入模式,在按下<esc>
后按下 Ctrl-v
,Vim 会打印 ^[
。 Ctrl-v
是一个插入模式的操作符,可以逐字地插入一个非数字字符。你的宏命令应该如下:
0W~$bideep fried ^[A.^[
为了在寄存器“a”中添加修改后的指令,你可以通过在一个已知寄存器中添加一个新入口的方式来实现。在一行的行首,执行 "ay$
。这将会告诉 Vim 你打算使用寄存器 “a” ("a
) 来存储从当前位置到行末的文本(y$
)。
现在,但你执行 @a
时,你的宏命令会自动改变第一个单词的大小写,在“donut”前面添加“deep fried”,之后在行末添加“.”。
另一个修改宏命令的方式是通过命令行解析。执行 :let @a="
,之后执行 Ctrl-r Ctrl-r a
,这会将寄存器“a”的命令逐字打印出来。最后,别忘记在闭合的引号("
)。如果你希望在编辑命令行表达式时插入内部码来使用特定的字符,你可以使用 Ctrl-v
。
你可以很轻松的将一个寄存器的内容拷贝到另一个寄存器。例如,你可以使用 :let @z = @a
将寄存器“a” 中的命令拷贝到寄存器“z”。 @a
表示寄存器“a”中存储的内容,你现在执行 @z
,将会执行和 @a
一样的指令。
我发现对常用的宏命令创建冗余是很有用的。在我的工作流程中,我通常在前7个字母(a-g)上创建宏命令,并且我经常不加思索地把它们替换了。因此,如果我将很有用的宏命令移动到了字母表的末尾,就不用担心我在无意间把他们替换了。
Vim 可以连续和同时运行宏命令,假设你有如下的文本:
import { FUNC1 } from "library1";
import { FUNC2 } from "library2";
import { FUNC3 } from "library3";
import { FUNC4 } from "library4";
import { FUNC5 } from "library5";
假如你希望把所有的 “FUNC” 字符变为小写,那么宏命令为如下:
qa0f{gui{jq
分解如下:
qa
开始记录宏命令到 “a” 寄存器。0
移动到第一行。f{
查找第一个 “{” 字符。gui{
把括号内的文本(i{
)变为小写(gu
)。j
移动到下一行。q
停止记录宏命令。
现在,执行 99@a
在剩余的行修改。然而,假如在你的文本里有如下 import 语句会怎么样呢?
import { FUNC1 } from "library1";
import { FUNC2 } from "library2";
import { FUNC3 } from "library3";
import foo from "bar";
import { FUNC4 } from "library4";
import { FUNC5 } from "library5";
执行 99@a
,会只在前三行执行。而最后两行不会被执行,因为在执行第四行(包含“foo”)时会遇到错误而停止。然而这种情况你希望继续向下执行。你可以移动到包含(“FUNC4”)的一行,并重新调用该命令。但是假如你希望仅调用一次命令就完成所有操作呢?你可以并行地执行宏命令。
如本章前面所说,可以使用 :normal
去执行宏命令,(例如: :3,5 normal @a
会在 3-5行执行 a 寄存器中的宏命令)。如果执行 :1,$ normal @a
,会在所有除了包含有 “foo” 的行执行,而且它不会出错。
尽管本质上来说,Vim 并不是在并行地执行宏命令,但表面上看,它是并行运行的。 Vim 会独立地在从第一行开始(1,$
)每一行执行 @a
。由于 Vim 独立地在每一行执行命令,每一行都不会知道有一行(包含“foo”)会遇到执行错误。
你在编辑器里做的很多事都是重复的。为了更好地编辑文件,请乐于发现这些重复性的行为。执行宏命令或者点命令,而不是做相同的动作两次。几乎所有你在 Vim 所作的事情都可以变为宏命令。
刚开始的时候,我发现宏命令时很棘手的,但是请不要放弃。有了足够的练习,你可以找到这种文本自动编辑的快乐。
使用某种助记符去帮助你记住宏命令是很有帮助的。如果你有一个创建函数(function)的宏命令,你可以使用 “f” 寄存器去录制它。如果你有一个宏命令去操作数字,那么使用寄存器 “n” 去记住它是很好的。用你想执行的操作时想起的第一个字符给你的宏命令命名。另外,我发现 “q” 是一个很好的宏命令的寄存器,因为执行 “qq” 去调用宏命令是很快速而简单的。最后,我喜欢按照字母表的顺序去添加我的宏命令,例如从 qa
到 qb
再到 qc
。去寻找最适合你的方法吧。