第21章 多文件操作
多文件编辑更新是一个值得掌握、非常有用的编辑工具。前面您已经学会了如何使用 cfdo
命令在多个文本中进行更新。本章,您将学到如何在Vim中进行多文件编辑的更多不同方法。
在多个文件中执行命令的几种方法
要在多个文件中执行命令,Vim有8种方法:
- 参数列表 (
argdo
) - 缓冲区列表 (
bufdo
) - 窗口列表 (
windo
) - tab 列表(
tabdo
) - 快速修复列表 (
cdo
) - 文件方式的快速修复列表 (
cfdo
) - 位置列表 (
ldo
) - 文件方式的位置列表 (
lfdo
)
实际上,大部分时间您可能只会用到1种或2种(就我个人而言,我使用 cdo
和 argdo
比其他的多得多),但了解所有可行方法还是很有用的,这样您就可以选择一个最符合您个人编辑风格的方法。
学习所有8个命令可能听起来让人有点打退堂鼓。但实际上,这些命令工作方式很相似。学习了其中一个后,再学习剩余的将容易的多。它们的运行方式都大体相同:分别创建一个列表(列表中的元素根据命令有所不同),然后向列表传递一个您想执行的命令。
参数列表
参数列表是最基础的列表。它创建一个文件列表。要想为 file1, file2, file3创建文件列表,您可以执行:
:args file1 file2 file3
您也可以传递一个通配符(*
),所以如果您想为当前目录下所有的 .js
文件创建一个列表,运行:
:args *.js
如果您想为当前目录下所有以 "a" 开头的Javascript文件创建列表,运行:
:args a*.js
(*
)通配符匹配当前目录下的1个或多个任意文件名中的字符。但如果您想在某个目录下进行递归搜索怎么办呢?您可以使用双通配符(**
)。要得到您当前位置下所有子目录中的Javascript文件,运行:
:args **/*.js
您运行了 args
命令后,您的当前buffer将会切换到列表中的第一个文件。运行 :args
可以查看您刚才创建的文件列表。当您创建好了您的列表后,您就可以遍历它们了。:first
将让您跳至列表中的第一个文件。:last
将跳到最后一个文件。运行:next
可以在列表中一次向前移动一个文件。运行 :prev
可以在列表中一次向后移动一个文件。运行:wnext
和 :wprev
命令,在向前/向后移动文件的同时还会保存修改。查阅 : arglist
了解更多导航命令。
参数列表在定位某个特定类型的文件或少量文件时特别有用。假如您需要将所有 yml
文件中的donut
更新为 pancake
。运行:
:args **/*.yml
:argdo %s/donut/pancake/g | update
注意如果您再次执行 args
命令,它将覆盖先前的列表。比如,如果您先前运行了:
:args file1 file2 file3
假设这些文件都是存在的,那么现在您的列表为 file1
, file2
,以及 file3
。然后再运行:
:args file4 file5
您的初始列表 file1
, file2
, file3
将被覆盖为 file4
, file5
。如果您的参数列表中已经有了 file1
, file2
, file3
,而您想将 file4
, file5
添加到初始列表中,请使用 :arga
命令。运行
:arga file4 file5
现在您的列表为file1
, file2
, file3
, file4
, file5
。
如果您运行 :arga
时没有给任何参数,Vim会添加当前buffer到参数列表中。例如,如果您的参数列表中已经有了 file1
, file2
, file3
,而您当前buffer是 file5
,运行 :arga
将添加 file5
到您的列表中。
在前面的命令(:argdo %s/donut/pancake/g
)中您已经看到过了,当您创建好列表后就可以向它传递任意命令行命令。其他的一些示例:
- 删除参数列表所有文件内包含 "dessert" 的行, 运行
:argdo g/dessert/d
. - 在参数列表每个文件中执行宏a(假设您已经在a中录好了一个宏),运行
:argdo norm @a
. - 向参数列表所有文件的第一行插入"hello "+文件名 ,运行
:argdo 0put='hello ' . @%
(译者注:在英文版中,原作者给出的命令是:argdo 0put='hello ' .. @:
,貌似这个命令有问题)。
把所有工作完成后,别忘了使用 :update
命令保存(:update
只会保存当前buffer,要保存列表所有文件的修改,请用 :argdo update
)。
有时候您仅仅需要在参数列表的前n个文件执行某条命令。如果是这种情况,只需要向 argdo
命令传递一个地址就可以了。比如,要在列表的前3个文件执行替换命令,运行::1,3argdo %s/donut/pancake/g
。
缓冲区列表
因为每次您创建新文件或打开文件时,Vim将它保存在一个buffer中(除非您显式地删除它),所以当您编辑新文件时,缓冲区列表就有组织地被创建了。如果您已经打开了3个文件:file1.rb file2.rb file3.rb
,您的缓冲区列表就已经有了3个元素。运行 :buffers
(或者:ls
、或:files
)可以显示缓冲区列表。要想向前或向后遍历缓冲区列表,可以使用 :bnext
:bprev
。要想跳至列表中第一个或最后一个buffer,可使用 :bfirst
和 :blast
。
另外,这里有一个和本章内容不相关,但是很酷的缓冲区技巧:如果您的缓冲区有大量的文件,您可以使用 :ball
显示所有缓冲区。:ball
命令默认使用水平分割窗口进行显示,如果想使用垂直分割的窗口显示,运行::vertical ball
回到本章主题。在缓冲区列表中执行某个操作的方法与参数列表操作非常相似。当您创建好缓冲区列表后,您只需要在您想执行的命令前用 :bufdo
代替 :argdo
就可以了。例如,如果您想将缓冲区列表内每个文件中所有的 "donut" 替换为 "pancake"并保存修改,可以运行::bufdo %s/donut/pancake/g | update
。
窗口列表和选项卡(Tab)列表
窗口列表、选项卡列表的操作和参数列表、缓冲区列表同样非常相似。唯一的区别在于它们的内容和语法。
窗口操作作用在每一个打开的窗口上,使用的命令是 :windo
。选项卡(Tab)操作作用在每一个打开的选项卡上,使用的命令是 :tabdo
。可以查询 :h list-repeat
, :h :windo
和:h :tabdo
,了解更多信息。
比如,如果您打开了4个窗口(您可以使用 Ctrl-w v
打开一个垂直分割的窗口,也可以使用 Ctrl-w s
打开一个水平分割的窗口),然后您运行 :windo 0put = 'hello' . @%
,Vim将在所有打开的窗口的第一行输出 "hello"+文件名。
快速修复列表
在前面的章节中(第3章和第19章),我曾提到过快速修复(quickfix)。快速修复有很多作用,很多流行的插件都在使用快速修复提供的功能,因此值得花时间去理解它。
如果您是Vim新手,快速修复对于您可能是个新概念。回想以前您执行代码编译的时候,编译期间您可能遇到过错误,而这些错误都显示在一个特殊的窗口。这就是快速修复(quickfix)的由来。当您编译您的代码的时候,Vim会在快速修复窗口显示错误信息,您可以稍后去解决。许多现代语言已经不再需要进行显式地编译,但快速修复并没有被淘汰。现在,人们使用快速修复来做各种各样的事,比如显示虚拟终端的输入、存储搜索结果等。我们重点研究后者,存储搜索结果。
除编译命令外,某些特定的Vim命令也依赖快速修复接口。其中一种就是搜索命令,其使用过程中大量的使用了快速修复窗口,:vimgrep
和 :grep
都默认使用快速修复。
比如,如果您需要在所有的Javascript文件中递归地搜索 "donut",您可以运行:
:vimgrep /donut/ **/*.js
“donut”的搜索结果存储在快速修复窗口中。要查看快速修复窗口的结果,运行:
:copen
要关闭快速修复窗口,运行:
:cclose
在快速修复列表中向前或向后遍历,运行:
:cnext
:cprev
跳至第一个或最后一个匹配的元素,运行:
:cfirst
:clast
在前面我提到过,有两种快速修复命令:cdo
和 cfdo
。它们有什么区别?cdo
在修复列表中的每一个元素上执行命令,而 cfdo
在修复列表中的每一个文件上执行命令。
让我讲清楚一点,假设运行完上面的 vimgrep
命令后,您找到以下结果:
- 1 result in
file1.js
- 10 results in
file2.js
如果您运行 :cfdo %s/donut/pancake/g
, 这个命令将会在 file1.js
和 file2.js
上分别有效地运行一次%s/donut/pancake/g
. 它执行的次数与 匹配结果中文件的数量 相同。因为搜索结果中有2个文件,因此Vim在 file1.js
上运行一次替换命令,在 file2.js
上再运行一次替换命令。 尽管在第二个文件中有10个搜索结果,但 cfdo
只关注快速修复列表中有多少个文件。
而如果您运行 :cdo %s/donut/pancake/g
,这个命令将会在 file1.js
上有效运行一次,然后在 file2.js
上运行10次。它执行的次数与 快速修复列表中元素的数量 相同。因为在 file1.js
上找到1个匹配结果,在 file2.js
上找到10个匹配结果,因此它执行的总次数是11次。
由于您要在列表中运行的命令是 %s/donut/pancake/g
,所以使用 cfdo
命令是比较合理的。而使用 cdo
是不合理的,因为它将在 file2.js
中运行10次 %s/donut/pancake/g
命令(%s
已经是一个针对整个文件的替换操作)。一个文件运行一次 %s
就足够了。如果您使用 cdo
,则传给它的命令应当改为 s/donut/pancake/g
才是合理的。
那到底什么时候该用 cfdo
?什么时候该用 cdo
? 这应当想一想您要传递的命令的作用域,要看命令作用域是整个文件(比如 :%s
或 :g
)?还是某一行(比如 :s
或 :!
)?
位置列表
位置列表在某种意义上和快速修复列表很像。Vim也使用一个特殊的窗口来显示位置列表的信息。区别在于:您任何时候都只能有1个快速修复列表,而位置列表则是,有多少个窗口就可以有多少个位置列表。
假设您打开了两个窗口,其中一个窗口显示 food.txt
,而另一个显示 drinks.txt
。在 food.txt
里面,运行一个位置列表搜索命令 :lvimgrep
(:vimgrep
命令关于位置列表的一个变体)。
:lvim /bagel/ **/*.md
Vim将为 food.txt
所在 窗口创建一个位置列表,用于存储所有的bagel搜索结果。用 :lopen
命令可以查看位置列表。现在转到另一个窗口 drinks.txt
,运行:
:lvimgrep /milk/ **/*.md
Vim将为 drinks.txt
所在 窗口再创建一个 单独的位置列表,用于存储所有关于milk的搜索结果。
对于每个不同的窗口中您运行的位置命令,Vim都会单独创建一个位置列表。如果您有10个不同的窗口,您就可以有最多10个不同的位置列表。对比前面介绍的快速修复列表,快速修复列表任何时候都只能有1个。就算您有10个不同的窗口,您也只能有1个快速修复列表。
大多数位置列表命令都和快速修复列表命令相似,唯一不同就是位置列表命令有一个 l-
前缀,比如: :lvimgrep
, :lgrep
, 还有 :lmake
。在快速修复列表命令中与之对应的是: :vimgrep
, :grep
, 以及 :make
。操作位置列表窗口的方式和快速修复窗口也很相似::lopen
, :lclose
, :lfirst
, :llast
, :lnext
, 还有:lprev
,与之对应快速修复版本是::copen
, :cclose
, :cfirst
, :clast
, :cnext
, and :cprev
。
两个位置列表参数的多文件操作命令也和快速修复列表的多文件操作命令也很类似::ldo
和 :lfdo
。:ldo
对位置列表中每一个元素执行命令,而 :lfdo
对位置列表中每一个文件执行命令。可以查阅 :h location-list
了解更多信息。
在Vim中运行多文件操作命令
在编辑工作中,知道如何进行多文件操作是一个非常有用的技能。当您需要在多个文件中改变一个变量名字的时候,您肯定想一个操作就全部搞定。Vim有8种不同的方法支持你完成这个事。
事实上,您可能并不会用到所有8种方法。您会慢慢倾向于其中1中或2种。当您刚开始时,选择其中1个(我个人建议从参数列表开始 :argdo
)并掌握它。当您习惯了其中1个,然后再学下一个。您将会发现,学习第二个、第三个、第四个时要容易多了。记得要创造性的使用,即将它和其他各种不同命令组合起来使用。坚持练习直到您可以不经思考地高效的使用它。让它成为您的肌肉记忆。
就像前面已经说过的,您现在已经掌握了Vim的编辑功能。恭喜您!