1. VIM简介

Vim是一个类似于Vi的著名的功能强大、高度可定制的文本编辑器 , 在Vi的基础上改进和增加了很多特性。VIM是纯粹的自由软件。

Vim普遍被推崇为类Vi编辑器中最好的一个 , 事实上真正的劲敌来自Emacs的不同变体。1999Emacs被选为Linux world文本编辑分类的优胜者 , Vim屈居第二。但在20002Vim赢得了Slashdot Beanie的最佳开放源代码文本编辑器大奖 , 又将Emacs推至二线 , 总的来看 , VimEmacs同样都是非常优秀的文本编辑器。

全屏幕的编辑器 : VIM , 又称visual interface , 是一款文本编辑器。

1.1 vim命令

  • 语法 : vim [OPTION]... FILE...

  • OPTIONS

    • +# : 打开文件后,让光标处于第#行的行首,+默认行尾

    • -x file : 编辑加密的文件

    • +/PATTERN : 让光标处于第一个被PATTERN匹配到的行行首

    • -b file : 二进制方式打开文件

    • -d file1 file2… : 比较多个文件,相当于vimdiff

    • -m file : 只读打开文件

    • -e file : 直接进入ex模式 , 相当于执行ex file

    • -m : 可以在文件中编辑,但是编辑后的内容无法保存

    • -M : 文本无法修改,同样也无法保存

    • -y file : Easy mode (like "evim", modeless),直接可以操作文件

      • Evim(Easy Vim)是一个特殊的GUI模式用来尽量的表现的和”无模式”编辑器一样。编辑器自动进入并且停留在插入模式 , 用户只能通过菜单、鼠标和键盘控制键来对文本进行操作。可以在命令行下输入evim或者vim -y进入。

      • ctrl+o : 快捷键用于从插入模式进入常规模式,类似于ESC键,所不同的是按了这个键进入常规模式执行完命令以后会返回编辑模式。

      • ctrl+o:wq|q! 保存和不保存退出

1.2 标记说明

当你看到诸如<C-p>这样的键时,它的意思不是“先按 <,然后按C,再按 -,等等”。<C-p>标记等同于 Ctrl-p,意为“同时按<Ctrl>p”。

我不会无缘无故地选择这种标记方式的。首先,在 Vim 的文档中使用了这种标记(:h key-notation ),我们也用它定义自定义按键映射项。另外,某些 Vim 命令由组合键及其他键以一定的次序组合在一起 , 这种标记也可以很好地表达这些命令。请看下面这些例子

标记

含义

<C-n>

同时按<Ctrl>n

g<C-]>

g,然后同时按<Ctrl>]

<C-r>0

同时按<Ctrl>r,然后按0

<C-w><C-=>

同时按<Ctrl>w,然后同时按<Ctrl>=

很多Vim命令需要以一定的次序按两个或多个按键。有些命令后面必须跟某种特 定类型的按键 , 而其他命令后面则可以跟键盘上的任意键。我使用花括号表示一条命令后可以跟有效按键集合。下面是一些例子:

标记

含义

f{char}

f后面跟任意字符

`{a-z}

按 `,后面跟任意小写字母

m{a-zA-Z}

m,后面跟任意小写或大写字母

d{motion}

d,后面跟任意动作命令

<C-r>{register}

同时按 <Ctrl>r,后面跟一个寄存器地址

有些特殊按键以其名字表示 , 下表节选了其中的一些:

标记

含义

<Esc>

按退出键

<CR>

按回车键 , 也写作 <Enter>

<Ctrl>

按控制键

<Tab>

按制表键

<Shift>

按切换键

<S-Tab>

同时按<Shift> <Tab>

<Up>

按上光标键

<Down>

按下光标键

按空格键

注:空格由表示。它和f{char}命令组合在一起时记为 f␣

2 vim模式

Vim具有6种基本模式和5种派生模式。在各种模式中,一般情况下每当切换到一个新的模式中时,vim左下角会显示有具体的模式显示信息。

2.1 常用模式

2.1.1 普通模式

在普通模式中,用的编辑器命令,比如移动光标、删除文本等等。这也是Vim 启动后的默认模式Vim 强大的编辑能力来自于其普通模式命令。普通模式命令往往需要一个操作符结尾。例如普通模式命令dd删除当前行 , 但是第一个d的后面可以跟另外的移动命令来代替第二个d,比如用移动到下一行的j键就可以删除当前行和下一行。另外还可以指定命令重复次数 , 2dd(重复dd两次),和dj的效果是一样的。在普通模式中 , 有很多方法可以进入插入模式。比较普通的方式是按a(append/追加)键或者i(insert/插入)键。

在屏幕中输入下面任意一键,vim便会进入插入模式

按键

功能

a

在光标后面进行插入

A

在当前光标所在行末尾插入

i

在光标所在处插入

I

在当前光标所在行首插入

o

在当前行下面新建一行,并进行插入

O

在当前行上面新建一行,并进行插入

s

删除光标后的一个字符,然后进入插入模式

S

删除光标所在行,然后进入插入模式

大多数普通模式命令可以在执行时指定次数 , 我们可以利用这个功能来做简单的算术运算。

很多普通模式命令都可以带一个次数前缀 , 这样 Vim 就会尝试把该命令执行指定的次数 , 而不是只执行一次(参见 :h count )。

<C-a><C-x> 命令分别对数字执行加和减操作。在不带次数执行时,它们会逐个加减,但如果带一个次数前缀,那么就可以用它们加减任意整数。例如,如果我们把光标移到字符5上 , 执行 10<C-a>就会把它变成 15

如果光标不在数字上,那么<C-a> 命令将在当前行正向查找一个数字,如果找到了,它就径直跳到那里,并将找到的数字加1。我们可以利用这一点简化操作。 <C-x> 则是正向查找一个数字,如果找到,则会跳转过去,并将找到的数字减1。

2.1.2 插入模式

在这个模式中 , 大多数按键都会向文本缓冲区中插入文本。这个模式主要用于编辑文本文件。在插入模式中 , 可以按ESC键或者<C-[>回到普通模式。

在插入模式中快捷键如下:

快捷键

作用

<C-m><C-j>

在光标上方插入一行空行

<C-e>

将光标正下方的一个字符,插入到光标所在处

<C-y>

将光标正上方的一个字符,插入到光标所在处

<C-a>

在光标所在处,插入上次插入过的文本

<C-2>

插入上次插入的文本然后返回普通模式

<C-r>

插入寄存器的内容

<C-w>

删除光标之前的一个单词

<C-u>

删除至行首

<C-t>

在当前行行首添加一个制表符缩进

<C-d>

从当前行行首删除一个制表符缩进,如果没有缩进则无效果。

<C-h>

删除光标前一个字符

<C-[>

返回普通模式,相当于esc

<C-i>

在光标前增加制表符[Tab]

<C-c>

返回普通模式

插入-普通模式

插入-普通模式是普通模式的一个特例,它能让我们执行一次普遍模式命令。在此模式中 , 我们可以执行一个普通模式命令 , 执行完后 , 马上就又返回到插入模式。

要从插入模式切换到插入-普通模式,可以按<C-o>(参见:h i_CTRL-O)。在当前行正好处于窗口顶部或底部时 , 有时我会滚动一下屏幕 , 以便看到更多的上下文。用zz 命令可以重绘屏幕 , 并把当前行显示在窗口正中,这样就能够阅读当前行之上及之下的半屏内容。我常常会键入<C-o>zz,在插入-普通模式中触发这条命令。此操作完成后就会直接回到插入模式 , 因此我可以不受中断地继续打字。

用字符编码插入非常用字符

只要知道某个字符的编码 , 就可以让Vim 插入该字符 , 我们可以用这种方式插入任意字符。要根据字符编码插入字符 , 只需在插入模式中输入<C-v>{code}即可 , 其中{code}是要插入字符的编码。

Vim 所接受的字符编码共包含3 位数字。例如 , 假设我们想插入大写字母A , 它的字符编码是65 , 因此我们需要输入<C-v>065

然而 , 如果我们想插入一个编码超过3 位数的字符该怎么办?比方说,Unicode基本多文种平面(Unicode Basic Multilingual Plane)的地址空间最大会有65535 个字符。解决方法是可以用4 位十六进制编码来输入这些字符,即输入<C-v>u{1234}(注意:数字前的u)。假设我们想插入字符编码为00bf的反转问号(¿ ) ,只需在插入模式中输入<C-v>u00bf 即可。更多详细内容可参见:h i_CTRL-V_digit

如果你想知道文档中任意字符的编码,只需把光标移到它上面并按ga命令,然后屏幕下方就会显示出一条消息 , 分别以十进制和十六进制的形式显示出其字符编码(参见:h ga)。

我们也可以用二合字母(digraph)来插入非常用字符,成对的字符更容易记忆一些。二合字母用起来很方便。在插入模式中,只需输入<C-k>>{char1}{char2} 即可。因此 , 如果想输入以二合字母 ?I 表示的¿字符 , 我们可以简单地输入<C-k>?I Vim 在选择组成二合字母的两个字符时,尽量使之具有描述性,这样就更容易记住它们 , 甚至也能猜出其含义。例如 , 左右书名号«»分别以二合字母<<>>表示 , 普通分数 ( 或常用分数 ) ½¼¾则分别以二合字母1214 34 来表示。Vim 的缺省二合字母集依从一定的惯例,:h digraphs-default 文档对此进行了总结。

按键操作

用途

<C-v>{123}

以十进制字符编码插入字符

<C-v>u{1234}

以十六进制字符编码插入字符

<C-v>{nondigit}

按原义插入非数字字符

<C-k>{char1}{char2}

插入以二合字母{char1}{char2}表示的字符

2.1.3 可视模式

Vim 具有3 种不同的可视模式 , 分别用于操作字符文本行文本块文本

可视模式简单来说就是选中一块编辑区域 , 然后在上面执行一些操作 , 比如删除 , 替换 , 改变大小写等。

这个模式与普通模式比较相似。但是移动命令会扩大高亮的文本区域。高亮区域可以是字符、行或者是一块文本。当执行一个非移动命令时 , 命令会被执行到这块高亮的区域上。

在普通模式下,按v 可激活面向字符的可视模式,按V(vShift 键一起按)可激活面向行的可视模式 , 而按<C-v>(v Ctrl 键一起按)则可激活面向列块的可视模式。

命令

用途

v

激活面向字符的可视模式

V

激活面向行的可视模式

<C-v>

激活面向列块的可视模式

gv

重选上次的高亮选区

o

切换高亮选区的活动端

示例

  1. 如下图所示操作时,先使用可视模式选中两行之后 , 使用>缩进一次,之后使用.重复之前的缩进操作。其实可以直接再使用>进行第二次缩进。 vim-view-mode-1

  2. 下图中 , 在块可视化模式中直接选中所有行的images之后 , 直接在可视化模式中使用c命令将第一行的中添加components之后退出之后,默认会在剩余两行中也会在选中的行中添加components vim-view-mode-2

  3. 确定好选区后 , 用A 命令就可以在每行的结尾添加内容(ia键在可视化模式中用于跳转)。此命令让我们进入插入模式,且使光标停留在顶行。处于插入模式期间,任何输入的内容只出现在顶行,然而一旦返回到普通模式,这些修改就会被扩散到其余选中的行上。 vim-view-mode-3

2.1.4 命令行模式

在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/?键 ) 或者过滤命令(!键)。

在按下:键时 , Vim 会切换到命令行模式。这个模式和shell下的命令行有些类似,我们可以输入一条命令,然后按<CR>执行它。在任意时刻,我们都可以按<Esc>键从命令行模式切换回普通模式。

出于历史原因,在命令行模式中执行的命令又被称做 Ex命令。在我们按/ 调出查找提示符或用<C-r>=访问表达式寄存器时 , 命令行模式也会被激活。

我们可以用Ex命令读写文件(:edit:write),创建新标签页(:tabnew)及 分割窗口(:split),或是操作参数列表(:prev/:next)及缓冲区列表(:bprev/:bnext)。事实上,Vim 为几乎所有功能都提供了相应的Ex 命令 ( 参见:h ex-cmd-index可获得完整列表)。

: read可以读取指定路径的文件内容到缓冲区内。: write用于保存缓存区内容到磁盘中,或者另一个文件中

可以使用:quit退出vim编辑器,可简写为:q。直接输入:quit!表示强制退出vim编辑器并丢弃做出的修改。在quitwrite一起使用时(:wq),表示保存修改内容并退出vim编辑器,:x同样可以实现保存并退出的效果。

普通模式中输入ZZ可以实现保存修改内容并退出的效果,ZQ则强制退出不会保存修改内容。

命令

用途

:[range]delete [x]

删除指定范围内的行[到寄存器x 中]

:[range]yank [x]

复制指定范围的行[到寄存器x 中]

:[line]put [x]

在指定行后粘贴寄存器x 中的内容

:[range]copy {address}

把指定范围内的行拷贝到{address} 所指定的行之下

:[range]move {address}

把指定范围内的行移动到{address} 所指定的行之下

:[range]join

连接指定范围内的行

:[range]normal {commands}

对指定范围内的每一行执行普通模式命令{commands}

:[range]substitute/{pattern}/{string}/[flags]

把指定范围内出现{pattern}的地方替换为{string}

:[range]global/{pattern}/[cmd]

对指定范围内匹配{pattern}的所有行 , 在其上执行Ex 命令{cmd}

在使用ex命令时需要使用[range],来确定ex命令在哪几行生效。

  • :# : 具体第#行 , 例如2表示第2行

  • :#,# : 从左侧#表示起始行 , 到右侧#表示结尾行

  • :#,+# : 从左侧#表示的起始行 , 加上右侧#表示的行数。:2,+3 : 表示2到5行

  • :.,$-1 : 当光标前行到倒数第二行

  • :/pat1/,/pat2/ :从第一次被pat1模式匹配到的行开始,一直到第一次被 pat2匹配到的行结束 。

  • :#,/pat/ : 从第#行开始,一直到第一次匹配到pat行结束。

  • :/pat/,$ : 从第一次被pat模式匹配到的行开始一直到最后一行

下表中表示有特殊含义的行含义:

符号

地址

1

文件的第一行

$

文件的最后一行

0

虚拟行 , 位于文件第一行上方

.

光标所在行

'm

包含位置标记m 的行

'<

高亮选区的起始行

'>

高亮选区的结束行

%

整个文件,(:1,$)的简写形式

0行在文件中并不真实存在,但它作为一个地址,在某些特定场景下会很有用处。主要是将片段复制到或者移动到第一行之前

如果想在一系列连续行上执行一条普通模式命令,我们可以用:normal 命令。此命令在与. 命令或结合使用时 , 我们只需花费很少的努力就能完成大量重复性任务。 vim-ex-mode-1

:'<,'>normal . 命令可以解读为“对高亮选区中的每一行,对其执行普通模式下的. 命令”。无论是操作5 行还是50 行文本 , 这种方法都能出色地完成任务,更棒的是我们甚至都不需要计算行数,在可视模式中选中这些行使我们摆脱了计数的负担。上图这个例子使用:normal 执行. 命令,但是也可以用这种方式执行任意其他的普通模式命令。

:%normal A; :符号%代表整个文件范围,因此:%normal A; 告诉Vim 在文件每行的结尾都添加一个分号。在做此修改时会切换到插入模式,但是在修改完后Vim 会自动返回到普通模式。

. 命令可以重复上次的普通模式命令。然而,如果想重复上次的 Ex 命令的话,我们得使用@: 才行。

在命令行模式中如同在shell 中一样,在命令行上也可以用<Tab> 键自动补全命令。<C-d> 命令会让Vim 显示可用的补全列表(参见:h c_CTRL-D )。另外,如果 我们多次按<Tab> 键的话,命令行上会依次显示coldercolorscheme,然后再回 到最初的col , 如此循环往复。要想反向遍历补全列表 , 可以按<S-Tab>

:col<C-d>
colder       colorscheme

2.1.4.1 把当前单词插入到命令行

<C-r><C-w> 映射项会复制光标下的单词并把它插入到命令行中。我们可以利用这一功能减少击键的次数。

假设我们想把下面这段代码中的变量tally 重命名为counter:

var tally;
for (tally=1; tally <= 10; tally++) {
// do something with tally
};

把光标移到单词tally 上后,用 * 命令就可以查找它出现的每处地方(* 命令等效于输入/\<<C-r><C-w>\><CR> 序列

vim-ex-mode-2

当按下* 键时,光标会正向跳到下一处匹配项,不过光标始终停留在相同的单词上。接下来,我们就可以输入cwcounter<Esc> 对其进行修改。

然后,我们将用:substitute 命令完成其余的修改。由于光标已经在单词counter上了,因此我们无需再次输入它,而是直接用<C-r><C-w>映射项把它插入到替换域:

:%s//<C-r><C-w>/g

<C-r><C-w> 用于插入光标下的单词,而如果想插入光标下的字串的话(参见技巧48的说明),我们可以用<C-r><C-a>,更多细节请参见:h c_CTRL-R_CTRL-W 。虽然本例是以:substitute 命令作为示例的 , 但实际上这些映射项可用于任意 Ex 命令

2.1.4.2 复制粘贴

  • :t命令复制行

    :copy命令(及其简写形式:t)让我们可以把一行或多行从文档的一部分复制到另一部分,而:move 命令(及其简写形式:m)则可以让我们把一行或多行移到文档的其他地方。通模式命令。

    copy 命令的格式如下(参见:h :copy):

    :[range]copy {address}

    我们可以把:6copy. 命令解读为“为第6 行创建一份副本 , 并放到当前行下方”。:copy 命令可以简写为两个字母:co,或者也可以用更加简练的:t命令,它是:copy 命令的同义词。

    命令

    用途

    :6t.

    把第6 行复制到当前行下方

    :t6

    把当前行复制到第6 行下方

    :t.

    为当前行创建一个副本 ( 类似于普通模式下的yyp )

    :t$

    把当前行复制到文本结尾

    :'<,'>t0

    把高亮选中的行复制到文件开头

  • :m命令移动行 :move 命令看上去和:copy 命令很相似(参见:h :move ):

    :[range]move {address}

    在选中高亮选区后 , 只需简单地执行命令:'<,'>m$ 即可。另外还有种做法,我们也可以执行dGp,此命令可以分解为:d 删除高亮选区,G 跳转到文件结尾 , 而p 则粘贴刚刚删除的文本。

2.1.4.3 运行Shell 命令

在 Vim 的命令行模式中,给命令加一个叹号前缀(参见:h :! )就可以调用外部程序。

注意区分:!ls:ls 的不同之处。前者调用的是shell中的ls 命令,而:ls调用的是Vim的内置命令,用来显示缓冲区列表的内容。

在用:!{cmd}时,Vim会回显{cmd} 命令的输出。如果命令的输出很少或没有输出,这工作得很好;但如果命令会产生大量输出,这样回显用处不大。另外一种做法是我们可以用:read !{cmd} 命令,把{cmd}命令的输出读入当前缓冲区中(参见:h read! )。

:read !{cmd} 命令让我们把命令的标准输出重定向到缓冲区。正如你所期望的一样, :write !{cmd} 做相反的事。它把缓冲区内容作为指定{cmd} 的标准输入(参见:h :write_c )

根据叹号在命令行上的位置不同,它的含义也不大相同。比较一下这些命令:

  • :write !sh

  • :write ! sh

  • :write! sh

前两个命令都会把缓冲区的内容传给外部的sh 命令作为标准输入,而最后一条命令则调用:write! 命令把缓冲区内容写到一个名为sh 的文件,这里的叹号会让Vim 覆盖任何已存的sh 文件。

当给定一个范围时,:!{cmd} 命令就具有了不同的含义。由[range]所指定的行会传给{cmd} 作为标准输入,然后又会用{cmd} 的输出覆盖[range]内原本的内容。换一种说法就是[range] 内的文本会被指定的{cmd} 进行过滤(参见:h :range! )。Vim 把过滤器定义为“一个由标准输入读取文本 , 并对其进行某种形式的修改后输出到标准输出的程序”。

作为演示,我们将用外部的sort 命令对下列记录进行排序:

first name,last name,email
john,smith,john@example.com
drew,neil,drew@vimcasts.org
jane,doe,jane@example.com

我们想基于第二个字段“姓氏”来重排这些记录。我们可以用-t',' 参数告诉sort 命令,这些记录以逗号分隔,然后再用-k2 参数指定按第二个字段进行排序。

因为文件的第一行是标题信息,我们想把它们保留在文件顶部,因此需要用范围:2,$ 把它排除在排序范围之外。下列命令将完成我们想要的功能:

:2,$!sort -t',' -k2

下面内容就是按姓氏排序的了 :

first name,last name,email
jane,doe,jane@example.com
drew,neil,drew@vimcasts.org
john,smith,john@example.com

Vim 提供了一种方便的快捷方式来设置:[range]!{filter} 命令中的范围。我们可以用!{motion} 操作符切换到命令行模式,并把指定{motion} 所涵盖的范围预置在命令行上(参见:h ! )。举个例子 , 如果我们把光标移到第2行,然后执行!G,Vim 就会打开命令行并把范围:.,$! 预置在命令行上。虽然此后我们仍需输入剩下的{filter} 命令,但这毕竟节省了部分工作。

下表选取了最有用的一些调用外部命令的方式:

命令

用途

:shell

启动一个shell (输入exit 返回Vim)

:!{cmd}

shell 中执行{cmd}

:read !{cmd}

shell 中执行{cmd},并把其标准输出插入到光标下方

:[range]write !{cmd}

shell 中执行{cmd},以[range] 作为其标准输入

:[range]!{filter}

使用外部程序{filter} 过滤指定的[range]

2.1.4.4 定制vim

在Linux中可以对vim进行定制化处理,通过定制化一个专属的vim特性可以让用户更方便操作。

这种定制化是可以通过两种方式:一种是通过配置文件进行永久的配置;另外一种就是通过命令行模式使用set命令进行临时配置

配置文件中有两个配置文件:全局配置文件,/etc/vimrc;和针对个人用的配置文件,~/.vimrc

下面列出常用的参数

  • (1) 行号

    • 显示 : set number, 简写为set nu

    • 取消显示 : set nonumber,简写为set nonu

  • (2) 忽略字符的大小写

    • 启用 : set ic

    • 不忽略 : set noic

  • (3) 自动缩进

    • 启用 : set ai

    • 禁用 : set noai

  • (4) 智能缩进

    • 启用 : set smartindent,简写set si

    • 禁用 : set nosi

  • (5) 高亮搜索

    • 启用 : set hlsearch

    • 禁用 : set nohlsearch

  • (6) 语法高亮

    • 启用 : syntax on

    • 禁用 : syntax off

  • (7) 显示Tab和换行符^I 和$显示

    • 启用 : set list

    • 禁用 : set nolist

  • (8) 文件格式

    • 启用windows格式 : set fileformat=dos

    • 启用unix格式 : set fileformat=unix,简写 : set ff=dos|unix

  • (9) 设置文本宽度

    • 启用: set textwidth=65 (vim only)

    • 禁用: set wrapmargin=15

  • (10) 设置光标所在行的标识线

    • 启用: set cursorline , 简写cul

    • 禁用: set no cursorline

  • (11) 复制保留格式

    • 启用: set paste

    • 禁用: set nopaste

2.1.5 选择模式

选择模式是另外一种的可视化模式,可以允许我们对选中的文本进行快速的删除和替换操作。我们可以高亮显示文本,然后用Backspace来删除这段文本;也可以高亮显示文本,然后用输入的内容来替换这些文本。 那么选择模式和可视化模式又有何区别?在可视化模式下,我们可以高亮显示选中的文本,然后执行命令操作。换句话说,我们要用命令来结束可视化模式。而在选择模式下,命令仅限于Backspace(用于操作删除操作)和可打印的字符(用于替换操作)。因为不需要输入命令,所以我们的操作变得更为简单,但同时他也有着很多限制。

在普通模式中可以使用以下命令进入到选择模式中。

  • gh : 进入字符选择模式

  • gH : 进入行选择模式

  • g<C-H> : 进入块选择模式

在选择模式下移动光标比在正常模式下要显得困难一些。因为输入任何可打印字符,vim就会删掉我们选中的文本并进插入状态。所以要选择文本我们只好使用方向键、CTRL以及功能键。如果要在选择模式下用鼠标来选择文本,需要设置以下选择:

:set selectmode=mouse

我们可以用<C-o><C-g>可以使vim在可视化模式选择模式间来回切换

2.1.6 ex模式

exUnix下的一个文本编辑器,vi就是exvisual mode

vi中的:w,:q!就是来自于ex命令。ex命令远不止这些 , 实际上所有:开头的vim命令,都属于ex命令。

在早期,显示器并不像现在这么好用,那会的“显示器”只有按行显示的功能。当你希望显示某行,输入命令 , “显示器”上才会显示那行。试着想想一个不支持上下左右滚动的界面。而控制这个“显示器”显示文本的命令行工具 , 就是ex。这样看来 , ex的历史比vi还要早!也可能是因为早 , 所以现在vim基本保留了早期ex的命令操作方式。

通过以下方式进入ex模式

[root@node01 ~]# vim -e anaconda-ks.cfg 
或者
[root@node01 ~]# ex anaconda-ks.cfg 

普通模式中直接输入Q也可以进入到ex模式中。

当需要退出ex模式时,直接输入visual即可

2.2 特殊模式

2.2.1 操作符等待模式

普通、插入及可视模式很容易辨识,但是Vim 还有另外一些很容易被忽视的模式,操作符待决模式(Operator-Pending mode)就是一个例子。每天我们无数次地使用它,但通常它只持续不到一秒时间。举个例子,在我们执行命令dw时,就会激活该模式。这一模式只在按dw键之间的短暂时间间隔内存在,一眨眼工夫就不见了。

如果我们把Vim 想象成有限状态机,那么操作符待决模式就是一个只接受动作命令的状态。这个状态在我们调用操作符时被激活 , 然后什么也不做,直到我们提供了一个动作命令,完成整个操作。当操作符待决模式被激活时,我们可以 像平常一样按<Esc> 中止该操作,返回到普通模式。

很多命令都由两个或更多的按键来调用(查阅:h g:h z:h ctrl-w,或者 :h [,可以看到一些例子),但在多数情况下 , 头一个按键只是第二个按键的前缀。这些命令不会激活操作符待决模式,相反,可以把它们当成命名空间(namespace),用来扩充可用命令的数目。只有操作符才会激活操作符待决模式。你也许想知道,为什么要有一个完整的模式,专门用于操作符和动作命令之间的短暂瞬间,而命名空间命令则仅仅是普通模式的一个扩充?好问题!这是因为我 们能够创建自定义映射项来激活或终结操作符待决模式。换句话说,它允许我们创建自定义的操作符及动作命令 , 从而让我们可以扩充Vim 的词汇。

n + 操作符 + 动作命令 = n次操作 = 操作符 + n + 动作命令

d{motion}命令可以对一个字符(dl)、一个完整单词(daw)或一整个段落(dap)进行操作,它作用的范围由动作命令决定。c{motion}y{motion}以及其他一些命令也类似 , 它们被统称为操作符(operator)。你可以用:h operator 来查阅完整的列 表,下表总结了一些比较常见的操作符。

命令

用途

c

修改

d

删除

y

复制到寄存器

g~

反转大小写

gu

转换为小写

gU

转换为大写

>

增加缩进

<

减小缩进

=

自动缩进

g~gugU 命令要用两次按键来调用,我们可以把上述命令中的g当做一个前缀字符,用以改变其后面的按键行为。

Vim的语法只有一条额外规则,即当一个操作符命令被连续调用两次时,它会作用于当前行。所以dd删除当前行等效于大写的D,而>>缩进当前行。gU 命令是一种特殊情况,我们既可以用gUgU ,也可以用简化版的gUU来使它作用于当前行。 cc表示删除整行并进入到插入模式,等效于大写的SC表示删除当前光标到行尾 , 并切换成插入模式 。yy表示复制整行等效于Y。同样可以在这些连续两次调用的操作符操作前通过添加数字的方式指定次数。如2dd表示光标所在行之后的删除两行

  • $ : 行尾

    • d$ : 表示删除到行尾

    • y$ : 表示复制到行尾

    • c$ : 表示删除到行尾,并进入到插入模式

其他的跳转动作命令类似 : ^0web

:

  1. p,P均是将寄存器中的内容在普通模式中添加到文本之后

  2. p : 缓冲区存的如果为整行,则粘贴当前光标所在行的下方;否则,则粘贴至当前光标所在处的后面

  3. P : 缓冲区存的如果为整行,则粘贴当前光标所在行的上方;否则,则粘贴至当前光标所在处的前面

  4. 由于上面的操作命令无法单独使用,所以无法单独使用dx可以单独使用,主要用于删除光标所在字符,#x表示删除光标所在出的#个字符。X表示向前删除字符。

  5. J : 删除当前行后的换行符

  6. 100iwang[Esc] :粘贴“wang”100次,从原理上面说在任何动作之前加入次数均可以进行多次操作。

2.2.2 替换模式

这是一个特殊的插入模式,在这个模式中可以做和插入模式一样的操作,但是每个输入的字符都会覆盖文本缓冲中已经存在的字符。在替换模式中输入会替换文档中的已有文本,除此之外,该模式与插入模式完全相同。

R命令可以由普通模式进入替换模式。完成替换后,就可以按<Esc> 键返回普通模式。如果你的键盘上有<Insert> 键,那么你也可以用该键在插入模式替换模式间切换,不过并非所有的键盘都有这个键。

虚拟替换模式

某些字符会使替换模式变得复杂化。以制表符为例,在文件中它以单个字符表示,但在屏幕上它却会占据若干列的宽度,此宽度由tabstop 设置决定(参见:h 'tabstop')。如果把光标移到制表符上,然后进入替换模式,那么我们所输入的下一个字符将会替换制表符。假设tabstop 选项设置为8(这是缺省值 ) ,那么该操作的结果就是把8个字符替换成了一个字符,这将大幅缩短当前行的长度。

虚拟替换模式(Virtual Replace mode)可在普通模式中由gR 命令触发,它会把制表符当成一组空格进行处理。假设我们把光标移到一个占屏幕8 列宽的制表符上,然后切换到虚拟替换模式,在输入前7 个字符时,每个字符都会被插入到制表符之前;最后,当输入了第8 个字符时,该字符将会替换制表符。

在虚拟替换模式中,我们是按屏幕上实际显示的宽度来替换字符的,而不是按文件中所保存的字符进行替换。这会减少意外情况的发生 , 因此我建议在可能的情况下尽量使用虚拟替换模式。

注:Vim 也提供了单次版本的替换模式及虚拟替换模式。r{char}gr{char} 命令允许我们覆盖一个字符,之后马上又回到普通模式(参见:h r )。这种小写的单次版本的替换模式同样适用于可视化模式。在可视化模式中替换一次字符之后会默认回到可视化模式中。

3. 文件操作

就像其他任一文本编辑器一样,Vim允许读取、编辑文件,并保存修改。在工作过程中,我们通常会说“我们正在编辑一个文件”,但真实情况并不是这样,我们所编辑的只是文件在内存中的映像,也就是 Vim 术语中的“缓冲区”。

文件是存储在磁盘上的,而缓冲区则存在于内存中。当Vim打开一个文件时,该文件的内容被读入一个具有相同名字的缓冲区。刚开始,缓冲区的内容和文件的内容完全相同,但当我们对缓冲区做出修改时,二者的内容就会出现差别。如果我们决定保留这些修改,就可以再把缓冲区的内容写回到文件里。绝大多数Vim命令都用来操作缓冲区,不过也有一些命令针对文件进行操作,这当中包括:write:update:saveas 命令。

Vim允许我们同时在多个缓冲区上工作。先在shell 里用下面的命令打开几个文件

[root@node01 dir]# ls
a.txt  b.txt
[root@node01 dir]# vim *.txt
2 files to edit

*.txt 通配符会匹配当前目录下的两个文件a.txtb.txt,因此上面的命令会让Vim打开这两个文件。当Vim启动时,它会显示一个窗口,窗口内的缓冲区对应着第一个文件。虽然另一个文件当前不可见,但其内容已经被载入到一个后台的缓冲区了,通过下面的命令可以看到这一点:

:ls
  1 %a   "a.txt"                        line 1
  2      "b.txt"                        line 0
Press ENTER or type command to continue

:ls 命令会列出所有被载入到内存中的缓冲区的列表(参见:h :ls)。用:bnext命令可以切换到列表中的下一个缓冲区:

:bnext<CR>
:ls
  1 #    "a.txt"                        line 1
  2 %a   "b.txt"                        line 1

% 符号指明哪个缓冲区在当前窗口中可见,而#符号则代表轮换文件。按<C-^>可以在当前文件和轮换文件间快速切换,在本例中,按一次会切换到a.txt,再按一次 , 就又回到b.txt了。

我们可以用4 条命令来遍历缓冲区列表。:bprev:bnext在列表中反向或正向移动,每次移动一项;而:bfirst :blast 则分别跳到列表的开头和结尾。

:ls 列表的开头有一个数字,它是在缓冲区创建时由Vim自动分配的编号。我们可以用:buffer N 命令直接凭编号跳转到一个缓冲区(参见:h :b ),或是用更直观的:buffer {bufname}格式实现同样的功能。{bufname}只需包含文件路径中足以唯一标识此缓冲区的字符即可。如果输入的字符串匹配了不止一个缓冲区列表中的条目 , 此时可以用Tab补全的方式在这些条目中选择。

每次打开一个文件时,Vim就会创建一个新的缓冲区。如果想删除缓冲区,可以用:bdelete命令,命令格式如下:

:bdelete N1 N2 N3
:N,M bdelete

注意: 删除一个缓冲区并不会影响缓冲区所关联的文件,而只是简单地把该文件在内存中的映像删掉。如果我们想删除编号5~10(包含510)的缓冲区,可以执行:5,10bd;然而,如果想要保留编号为8 的缓冲区的话,那么就只能用:bd 5 6 7 9 10 了。

缓冲区的编号由Vim自动分配,没有办法手动改变此编号。因此,如果想删除一个或多个缓冲区,先得进行一番查找以便找出它们的编号,而这一过程会比较耗时。因此,除非有充足的理由要删除某个缓冲区,否则我才不会去自找麻烦。这样一来,:ls列表中的文件就是我在此编辑会话中打开的所有文件。

Vim 内置的缓冲区管理功能缺乏灵活性。如果我们想对缓冲区进行组织,使其满足工作过程的需要,使用缓冲区列表并不是最佳选择。

3.1 管理文件

我们对a.txt 做点修改 , 按Go 在缓冲区的结尾增加一个空行。

:ls                                                                                                                                                               
  1 %a + "a.txt"                        line 2
  2      "b.txt"                        line 0

如果需要在多个文件中跳转,可以使用以下命令:

:next/bnext 下一个  
:prev/bprev 前一个  
:first/bfirst 第一个  
:last/blast 最后一个 

如果需要判断文件间跳转命令有哪些,可以直接在命令行模式中按下b之后直接按tab键即可看到。

缓冲区a.txt 前有一个+号,表示这个缓冲区被修改过了。如果现在保存文件的话,缓冲区的内容就会被写入磁盘里,而+号也会消失了。但是我们先不急着保存,而是试着切换一下缓冲区

:bnext
《 E37: No write since last change (add ! to override

此时,Vim 会弹出一条错误信息,说当前缓冲区中有未保存的改动。让我们试一下括号中的建议,在上述命令的结尾加一个叹号 :

:bnext!
:ls
  1 #h + "a.txt"                        line 2
  2 %a   "b.txt"                        line 1

叹号会强制 Vim 切换缓冲区,即使当前缓冲区中有未保存的修改,也会继续切换。如果现在再运行:ls 命令,就会发现b.txt被标记为a,表示它当前是活动缓冲区(active),而a.txt则被标记为h,表示它是一个隐藏缓冲区(hidden)。

当一个缓冲区被隐藏后,Vim允许我们像往常一样工作。我们可以打开其他缓冲区,对其进行修改、保存等,没有任何不同。也就是说,一直到尝试退出编辑会话前,一切如常。然而 , 当我们想关闭编辑会话时 , Vim 就会提醒我们某个缓冲区中有未保存的修改

:quit
E37: No write since last change
E162: No write since last change for buffer "a.txt"

Vim 会把第一个有改动的隐藏缓冲区载入当前窗口,这样我们就可以决定如何处理它。如果要保留修改,可以执行:write命令把缓冲区保存到文件;如果想摒弃此修改,可以执行:edit!,重新从磁盘读取此文件,这会用文件的内容覆盖缓冲区中的内容。当缓冲区内容与磁盘文件一致后 , 我们就可以再次尝试执行:quit 命令了。

如果会话里有不止一个被修改过的隐藏缓冲区 , 那么每次执行:quit命令时,Vim都会激活下一个未保存的缓冲区。同样的 , 我们可以用:write:edit!来保存或摒弃此修改。当没有其他窗口和隐藏缓冲区时,:q 命令就会关闭Vim

如果想退出 Vim 而不想对未保存的修改进行检查,可以执行:qall!命令;如果想保存所有有改动的缓冲区而无需逐个检查,可以用:wall 命令。

命令

用途

:w[rite]

把缓冲区内容写入磁盘

:e[dit]!

把磁盘文件内容读入缓冲区 ( 即回滚所做修改 )

:qa[ll]!

关闭所有窗口 , 摒弃修改而无需警告

:wa[ll]!

把所有改变的缓冲区写入磁盘

:wq[all]

退出并保存所有文件。

!符号是可选的

参数列表易于管理,适用于对一批文件进行分组,使其更容易访问。用:argdo命令可以在参数列表中的每个文件上执行一条 Ex 命令

[root@node01 dir]# vim *.txt
3 files to edit
​
:args
[a.txt] b.txt c.txt

参数列表记录了在启动时作为参数传递给Vim的文件列表。在本例中,我们只用了一个参数*.txt。然而,shell 会对*通配符进行扩展,使其匹配3个文件,这3个文件我们已经在参数列表中看到了。输出中的[]字符则指明了参数列表中的哪个文件是活动文件。

:ls 命令所显示的列表相比,:args命令的输出比较简陋。当不带参数运行:args命令时 , 它会打印当前参数列表的内容。另外,我们也可以用下列格式来设置参数列表的内容(参见:h :args_f):

:args {arglist}

{arglist} 可以包括文件名、通配符,甚至是一条 shell 命令的输出结果。

3.1.1 多窗口管理

Vim 允许我们将工作区切分成若干窗口,在这些窗口里并排显示多个缓冲区。

Vim 术语中,窗口是缓冲区的显示区域(参见:h window)。我们既可以打开多个窗口 , 在这些窗口中显示同一个缓冲区,也可以在每个窗口里载入不同的缓冲区。Vim 的窗口管理系统很灵活 , 我们可以根据工作的需要来调整工作区。

3.1.1.1 创建分割窗口

Vim 在启动时只会打开单个窗口。用<C-w>s命令可以水平切分此窗口,使之成为两个高度相同的窗口;或者可以用<C-w>v命令对其进行垂直切分,这样会产生两个宽度相同的窗口。这两条命令可以重复任意多次 , 结果就会把工作区一次次地切分为更小的窗口,就像细胞分裂那样。

每次执行完<C-w>s<C-w>v命令后,新生成的两个窗口都会显示与原窗口相同的缓冲区。把同一缓冲区显示在不同窗口里会很有用 , 特别是在编辑长文件时。举个例子,我们可以滚动其中一个窗口,使之显示缓冲区的一部分,这样,在修改第二个窗口中缓冲区的另外一部分时,就可以参考第一个窗口中的内容。

我们可以用:edit命令把另外一个缓冲区载入活动窗口中。如果先执行<C-w>s,再执行:edit {filename},就会把工作区分成两个窗口,并在其中一个窗口中打开新缓冲区,而另一个窗口则继续显示原有的缓冲区。另外一种做法是使用:split {filename}命令,它把上述两步合并成为了一步。

下表总结了把工作区切分为窗口的几种方式:

命令

用途

<C-w>s

水平切分当前窗口 , 新窗口仍显示当前缓冲区

<C-w>v

垂直切分当前窗口 , 新窗口仍显示当前缓冲区

:sp[lit] {file}

水平切分当前窗口 , 并在新窗口中载入{file}

:vsp[lit] {file}

垂直切分当前窗口 , 并在新窗口中载入{file}

3.1.1.2 窗口间切换

Vim 提供了一些在窗口间进行切换的命令,下表总结了其中最常用的一些命令:

命令

用途

<C-w>w

在窗口间循环切换

<C-w>h

切换到左边的窗口

<C-w>j

切换到下边的窗口

<C-w>k

切换到上边的窗口

<C-w>l

切换到右边的窗口

实际上,<C-w><C-w> 完成的功能和<C-w>w 相同,就是说我们可以一直按住<Ctrl> 键,然后再输入ww(或wj,或上表中的其他命令)来切换活动窗口。<C-w><C-w> 要比<C-w>w 更容易按一些,尽管写出来时它显得更繁琐。如果经常使用多个窗口 , 那么你可能需要考虑把这些命令映射成更方便的按键。

如果你的终端支持鼠标操作的话,或是你用的是GVim,那么你也可以通过鼠标点击来激活一个窗口。如果你用不了鼠标,请检查一下mouse 选项是否被正确设置了(参见:h 'mouse')。

3.1.1.3 关闭窗口

想减少工作区中窗口的数量,可以用两种方式:一是使用:close命令关闭活动窗口,二是用:only命令关闭除活动窗口外的所有其他窗口。下表总结了这两条命令,并列出了与之等效的普通模式命令 :

Ex 命令

普通模式

命令用途

:clo[se]

<C-w>c

关闭活动窗口

:on[ly]

<C-w>o

只保留活动窗口 , 关闭其他所有窗口

:q[uit]

<C-w>q

关闭相邻窗口

3.1.1.4 改变窗口大小及重新排列窗口

Vim 提供了一些用于改变窗口大小的按键映射项, 完整的列表请查阅:h window-resize,下表中列出了最常用的几个命令:

命令

用途

<C-w>=

使所有窗口等宽、等高

<C-w>_

最大化活动窗口的高度

<C-w>|

最大化活动窗口的宽度

[N]<C-w>_

把活动窗口的高度设为[N]行

[N]<C-w>|

把活动窗口的宽度设为[N]列

3.1.2 标签页

Vim 的标签页与缓冲区并非一一对应的关系,相反,应该把标签页想成容纳一系列窗口的容器。下图中显示了一个带有3个标签页的工作区,每个标签页都包含一个或多个窗口。图中灰色的方块代表了当前的活动窗口及活动标签页。

tab

假设我们正在处理某个工程中的文件,并且已经把工作区分成了几个窗口。然后,突然接到一项紧急任务,我们不得不马上切换工作内容。我们不想在当前标签页里打开新文件,因为这会把我们精心布置的工作区弄乱。此时,我们可以新创建一个标签页,并在此标签页中工作。当我们准备继续做原来的工作时 , 只需切回原来的标签页即可 , 所有的窗口都保持着我们离开时的模样。

3.1.2.1 打开及关闭标签页

:tabedit {filename} 命令可以打开一个新的标签页,如果省略了{filename} 参数的话,那么Vim会创建一个新标签页,里面包含一个空缓冲区。

还有一种做法 , 如果当前标签页中包含了不止一个窗口,我们可以用<C-w>T命令把当前窗口移到一个新标签页中(参见:h CTRL-W_T )。

如果活动标签页中只包含一个窗口,那么:close命令将关闭此窗口以及包含此窗口的标签页。我们也可以用:tabclose命令来关闭当前标签页,无论其中有多少个窗口。最后 , 如果想关闭除当前标签页外的所有其他标签页,可以用:tabonly 命令。

命令

用途

:tabe[dit] {filename}

在新标签页中打开{filename}

<C-w>T

把当前窗口移到一个新标签页

:tabc[lose]

关闭当前标签页及其中的所有窗口

:tabo[nly]

只保留活动标签页 , 关闭所有其他标签页

3.1.2.2 标签页间切换

标签页的编号从1开始,我们可以用{N}gt命令在标签页间切换,可以把此命令记成“跳到标签页{N}”。当此命令带一个数字前缀时,Vim会跳到指定编号的标签页;如果省略了数字前缀,则会跳到下一个标签页。gT 命令的功能与此相同,只是跳转方向相反。

Ex命令

普通模式

命令用途

:tabn[ext] {N}

{N}gt

切换到编号为{N} 的标签页

:tabn[ext]

gt

切换到下一标签页

:tabp[revious]

gT

切换到上一标签页

3.1.2.3 重排标签页

:tabmove [N] 命令可以重新排列标签页。当[N]0时,当前标签页会被移到开头;如果省略了[N],当前标签页会被移到结尾。如果你的终端支持鼠标,或是你正在使用GVim , 那么你也可以通过鼠标拖曳来进行重排操作。

3.2 打开与保存文件

Vim中,:edit命令允许通过文件的绝对路径或相对路径来打开文件。

在Vim中也有工作目录的概念,这和bash及其他shell是一样的。当Vim启动时,它会采用shell 的活动目录作为其工作目录。这一点可以通过执行:pwd命令得到印证,pwd意为“打印工作目录”(print working directory,这和bash是一样的)

edit {file}命令可以接受相对于工作目录的文件路径。因此,如果想打开lib/framework.js文件的话 , 可以执行下面这条命令:

:edit lib/framework.js

或者,用下面的命令就可以打开app/controllers/Navigation.js

:edit app/controllers/Navigation.js 

另外,我们也可以用Tab键自动补全文件路径。因此,如果想打开Navigation.js文件的,实际上只需输入:edit a<Tab>c<Tab> N<Tab>

这里需要说明

:edit %<Tab>

%符号代表活动缓冲区的完整文件路径(参见:h cmdline-special),按<Tab>键会将其展开,使之显示为活动缓冲区的完整文件路径。

:edit %:h<Tab>

:h修饰符会去除文件名,但保留路径中的其他部分(参见:h ::h)。在此例中,输入的 %:h<Tab>会被展开为当前文件所在目录的路径:

我们可以用:edit {path}命令打开文件管理器窗口,只是在执行此命令时要以目录名(而不是文件名)作为{path}参数。由于符号.代表了当前工作目录,因此,如果执行:edit . 命令,就会在文件管理器里打开工程的根目录。

Ex 命令

缩写

用途

:edit .

:e.

打开文件管理器 , 并显示当前工作目录

:Explore

:E

打开文件管理器 , 并显示活动缓冲区所在的目录

:edit {file}命令一般用于打开一个已存在的文件。然而,如果我们指定了一个不存在的文件路径,那么Vim就会创建一个新的空白缓冲区。如果我们此时按<C-g>,就会看到该缓冲区被标识为“新文件” ( <C-g>命令用于显示当前文件的文件名及状态,参见 :h ctrl-G)。随后 , 当我们执行:write命令时,Vim就会尝试将此缓冲区的内容写到创建该缓冲区时所指定的文件路径中。

:edit madeup/dir/doesnotexist.yet 
:write 
"madeup/dir/doesnotexist.yet" E212: Can't open file for writing

在本例中,madeup/dir目录并不存在,不过Vim依然会创建一个新缓冲区,只是这一次该缓冲区被标识为“新目录”了。不过,当我们试图把缓冲区写入磁盘时,Vim会显示一条出错信息。在这种情况下,我们可以调用外部的mkdir程序对此做出补救 :

:!mkdir -p %:h 
:write

3.3 文件内跳转

动作命令是进行Vim操作的最重要的一些命令。我们不仅可以用它们四处移动光标,还能够用它们与操作符待决模式配合使用,指定一段文本范围并在其上进行操作。查阅 Vim 文档中的:h motion.txt,以获得一个完整的动作命令列表。

  • 字符间跳转 jk命令会根据实际行向下及向上移动,而gjgk则是按屏幕行向下及向上移动

    命令

    光标动作

    h

    左移一列

    l

    右移一列

    j

    下移一行

    gj

    向下移动一个屏幕行

    k

    上移一行

    gk

    向上移动一个屏幕行

    0

    移动到实际行的行首

    g0

    移动到屏幕行的行首

    ^

    移动到实际行的第一个非空白字符

    g^

    移动到屏幕行的第一个非空白字符

    $

    移动到实际行的行尾

    g$

    移动到屏幕行的行尾

  • 单词间跳转 在vim中单词由字母、数字、下划线或其他非空白字符组成,组成单词的字符类型必须是同一种类型的字符。单词间以空白字符分隔(参见 :h word)。而字串的定义则更简单,它由非空白字符序列组成,字串间以空白字符分隔(参见 :h WORD)。 下列字符中vim分别识别单词和字符串

    e.g. we're going too slow

    其中字符串会识别为5个,分别为e.g.we'regoingtooslow

    单词识别为e.g.we'regoningtooslow

    命令

    光标动作

    w

    正向移动到下一单词的开头

    b

    反向移动到当前单词/上一单词的开头

    e

    正向移动到当前单词/下一单词的结尾

    ge

    反向移动到上一单词的结尾

    同样的如果要实现字符串之间的跳转可以输入以下命令:W,B,E,gE。效果和小写的类似,只不过效果则是字串间的跳转。

  • 对行内字符进行查找 Vim 的字符查找命令让我们可以在行内快速移动 , 并且它们能够在操作符待决模。f{char} 命令是在Vim中移动的最快方式之一。它会在光标位置与当前行行尾之间查找指定的字符,如果找到了,就会把光标移到此字符上;如果未找到,则保持光标不动(参见:h f)。 vim-fmode-1 字符c在该行出现了好几次,因此这次我们无法直接命中目标,而是要试几次才能把光标移到想去的位置。幸运的是,我们不必一遍遍地重复输入fc命令。Vim会记录上次执行过的f{char}命令,随后用;命令就可以重复该命令了(参见:h ;)。在本例中,我们得连按3次;才能使光标就位。用,命令就可以再跳回来。

    命令

    用途

    f{char}

    正向移动到下一个 {char} 所在之处

    F{char}

    反向移动到上一个 {char} 所在之处

    t{char}

    正向移动到下一个 {char} 所在之处的前一个字符上

    T{char}

    反向移动到上一个 {char} 所在之处的后一个字符上

    ;

    重复上次的字符查找命令

    ,

    反转方向查找上次的字符查找命令

    同样可以通过使用/命令进入到命令行模式,之后输入单词来进行筛选,直接输入d/and<CR>,会删除掉and之前所有的内容

  • 用精确的文本对象选择选区 文本对象允许我们操作括号、被引用的文本、XML标签以及其他文本中的常见结构。 vim-aimode-1 当我们按下vi}时,Vim会进入可视模式,并选中花括号{}所括起来的所有字符。光标放在哪儿都没关系,只要在调用i}文本对象时,光标在花括号内部就行了。

    文本对象

    选择区域

    a)/ab

    一对圆括号(parentheses)

    i)/ib

    圆括号(parentheses)内部

    a}/aB

    一对花括号{braces}

    i}/iB

    花括号{braces}内部

    a]

    一对方括号[brackets]

    i]

    方括号[brackets]内部

    a>

    一对尖括号<angle brackets>

    i>

    尖括号<angle brackets>内部

    a'

    一对单引号'single quotes'

    i'

    单引号'single quotes'内部

    a"

    一对双引号"double quotes"

    i"

    双引号"double quotes"内部

    a`

    一对反引号 backticks

    i`

    反引号 backticks 内部

    at

    一对XML标签<xml>tags</xml>

    it

    XML标签内部

    Vim的文本对象分为两类:一类是操作分隔符的文本对象,如i)i"it;另一类用于操作文本块,如单词句子段落。上面讲述的便是文本对象。下面列表中则是文本块的说明。

    文本对象

    选择范围

    iw

    当前单词

    aw

    当前单词及一个空格

    iW

    当前字串

    aW

    当前字串及一个空格

    is

    当前句子

    as

    当前句子及一个空格

    ip

    当前段落

    ap

    当前段落及一个空行

    按之前说过的记忆方式,可以分别把它们解读为操作单词内部(inside the word)或单词周围(around the word)。iw文本对象包含当前单词从第一个到最后一个字符间的全部内容,aw文本对象也是一样,但它的范围有所扩大,它会额外包含该单词前面或后面的一个空白字符(假如该处有空白字符的话)。下面两个图即为使用示例 ciw命令只删除该单词,而不删除其前后的空白字符,随后它会进入插入模式,这刚好是我们想要的效果。如果用的是caw的话,那最后两个单词就会连在一起 , 变成mostadjectives

  • 通过标记跳转

    Vim 的位置标记允许我们快速跳转到文档中感兴趣的地方。我们可以手动设置位置标记,不过Vim也会自动帮我们记录某些感兴趣的位置点。m{a-zA-Z}命令会用选定的字母标记当前光标所在位置(参见:h m)。小写位置标记只在每个缓冲区里局部可见,而大写位置标记则全局可见,可以实现在文件间跳转。设置一个位置标记时,Vim不会用可见的标识表明它已被设置。不过如果你已经正确地设置好了,那么只用两个键你就能从文件的任何地方直接跳到该位置标记所在之处。同时标记中还有'{0-9}数字位置标记,数字位置标记不能被直接设置,而只能从viminfo文件中获取。'0是你上次退出Vim时的光标位置。'1是再上一次,依次递推。viminfo的 'r' 标志可以指定不记录数字位置标记的文件。

    '{mark}命令跳到位置标记所在行,并把光标置于该行第一个非空白字符上;而 `{mark} 命令则把光标移动到设置此位置标记时光标所在之处。 下表中显示vim中自动标识

    位置标记

    跳转到

    ``

    当前文件中上次跳转动作之前的位置

    `.

    上次修改的地方

    `^

    上次插入的地方

    `[

    上次修改或复制的起始位置

    `]

    上次修改或复制的结束位置

    `<

    上次高亮选区的起始位置

    `>

    上次高亮选区的结束位置

    g'{mark} / g`{mark}

    跳转到指定的位置标记 {mark},但在当前缓冲区内跳转时,不改变跳转表。

    :marks

    列出所有的位置标记(这不是动作命令),但不包括 '( , ') , '{ 和 '} 标记。第一列的编号为零。

    :marks {arg}

    列出所有 {arg}中所包含的位置标记,标记之间无须任何分隔符,如有则忽略(这不是动作命令)。

    :delm[arks] {marks}

    删除指定的位置标记。可以删除的位置标记也包括 A-Z0-9。不能删除 ' 位置标记。指定的方式包括给出位置标记名的列表和使用连字符分隔的范围。忽略空格。

  • 在匹配括号间跳转 Vim 提供了一个动作命令,让我们可以在开、闭括号间跳转。在激活了matchit.vim插件后,此命令也可以用于成对的XML标签,以及某些编程语言中的关键字上。 %命令允许我们在一组开、闭括号间跳转(参见:h %),它可作用于(){}以及[] vim-jump-replace-1

  • 快捷键跳转

    • Ctrl+f : 向文件尾部翻一屏

    • Ctrl+b : 向文件首部翻一屏

    • Ctrl+d : 向文件尾部翻半屏

    • Ctrl+u :向文件首部翻半屏

3.4 文件间跳转

Vim会记录跳转前后的位置,并提供了一些命令让我们能够沿原路返回。 <C-o>命令像后退按钮一样,而与之互补的<C-i>命令则像是前进按钮。这两条命令允许我们对 Vim 的跳转列表进行遍历。 让我们先这样区分,动作命令在一个文件内移动,而跳转则可以在文件间移动(虽然很快我们就会看到,有些动作命令也被归为跳转),我们可以用如下命令查看跳转列表的内容:

:jumps                                                         
jump line  col file/text
11     5   17 test
10     2    6 test
9     6    5 test
8   849   48 /usr/share/vim/vim80/doc/motion.txt
7     1    0 /usr/share/vim/vim80/doc/motion.txt
6   874    0 /usr/share/vim/vim80/doc/motion.txt
5    46   29 -invalid-
4     1    0 #version=RHEL8
3    44    9 pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
2     1    5 test
1    16    0 network  --bootproto=dhcp --device=ens33 --ipv6=auto --activate
>  0    45    0 %end

<C-o><C-i>命令在这个新文件以及原本的文件之间来回跳转。 用[count]G命令直接跳到指定的行号也会被当成一次跳转,但每次向上或向下移动一行则不算。面向句子的动作及面向段落的动作都算跳转,但面向字符及面向单词的动作则不算。用一句话来概括,我们可以说大范围的动作命令可能会被当成跳转,但小范围的动作命令则只能算移动。

命令

用途

[count]G

跳转到指定的行号

/pattern<CR>,/?pattern<CR>/n/N

跳转到下一个/上一个模式出现之处

%

跳转到匹配的括号所在之处

(/)

跳转到上一句/下一句的开头

{/}

跳转到上一段/下一段的开头

H/M/L

跳到屏幕最上方/正中间/最下方

gf

跳转到光标下的文件名

<C-]>

跳转到光标下关键字的定义之处

'{mark}/`{mark}

跳转到一个位置标记

试着在插入模式中按一下<C-i>,你会发现这和按<Tab>键的效果是一样的,因为 Vim 本来就把<C-i><Tab>当成同一个东西

同样使用u<C-r>可以使我们跳转回上次修改的地方。

  • u : 撤销最近的更改

  • #u :撤销之前多次更改

  • U : 撤消光标落在这行后所有此行的更改

  • <C-r> : 恢复撤销的内容或者操作

  • . : 重复前一个操作

  • n. : 重复前一个操作n

4. 寄存器

Vim 的寄存器是一组用于保存文本的简单容器。它们既可像剪贴板那样,剪切、复制和粘贴文本;也可以记录一系列按键操作,把它们录制成宏。

Vim 提供了几十组寄存器用于保存文本,而并非只用系统单一的剪贴板。通常情况下,在讨论剪切、复制与粘贴这3组操作时,指的都是操作系统剪贴板。不过 , 在 Vim 的术语里 , 我们操作的是寄存器 , 而并非剪贴板。

4.1 寄存器类型

4.1.1 无名寄存器

倘若我们没有指定要使用的寄存器,Vim将缺省使用无名寄存器,它可以用双引号表示示(参见 :h quote_quote)。为了显式地引用该寄存器 , 我们得使用两个双引号。例如,""p,它完全等同于p命令。

xsd{motion}c{motion}y{motion}命令(以及它们对应的大写命令)都会覆盖无名寄存器中的内容。无论哪一种情况,都可以通过加"{register}前缀来指定另外一个寄存器,但无名寄存器总是缺省的。

4.1.1.1 调换字母

vim-xp

这次错误源于我们敲空格键敲得太快了,不过这很容易改正。在纠错过程中 , 首先,F␣命令将光标移到要置换的第一个字符上。然后,x命令把光标下的字符剪切下来,把它放到无名寄存器。最后,p命令将无名寄存器中的内容粘贴到光标后面。 将最后两条命令组合在一起,即xp,可被用于“调换光标之后的两个字符”。

  • x : 删除光标处的字符

  • #x : 删除光标处起始的#个字符

  • xp : 交换光标所在处的字符及其后面字符的位置

4.1.1.2 调换文本行

vim-ddp

dd命令剪切当前行,从而将其内容存入无名寄存器中。p命令知道我们正在处理的是一整行文本,因此,如我们所愿,它把无名寄存器的内容粘贴至当前行的下一行

把以上命令序列连起来,即ddp,可被用于“调换当前行和它的下一行”。

4.1.1.3 创建文本行的副本

vim-yyp ddpyyp这两组按键操作的相似之处。前者是对文本行的剪切与粘贴操作 , 实际上是调换了两行的顺序。后者是针对行的复制与粘贴操作 , 即创建一行副本

无名寄存器只会保留最近剪切复制的内容

4.1.2 复制专用寄存器

当我们使用y{motion}命令时,要复制的文本不仅会被拷贝到无名寄存器中,而且也被拷贝到了复制专用寄存器("0)中,后者可用数字0(参见:h quote0)加以引用。

当我们使用y{motion}命令时,要复制的文本不仅会被拷贝到无名寄存器中,而且也被拷贝到了复制专用寄存器中,后者可用数字0(参见:h quote0 )加以引用。复制专用寄存器,顾名思义,仅当使用y{motion}命令时才会被赋值。换句话讲,使用xsc{motion}以及d{motion}命令均不会覆盖该寄存器。如果我们复制了一些文本 , 可以确信该文本会一直保存于寄存器0中,直到我们复制其他文本时才会被覆盖。复制专用寄存器是稳定的 , 而无名寄存器是易变的。

可以在命令行模式中使用以下命令查看复制专用寄存器的缓存内容

:reg "0
--- Registers ---
""   Practicavim^J
"0   Practicavim^J

4.1.3 有名寄存器

Vim提供了一组以26个英文字母(参见:h quote_alpha)命名的有名寄存器。这意味着我们可以剪切("ad{motion})、复制("ay{motion})或者粘贴("ap)多达26 段文本。

使用有名寄存器需要额外的按键操作,因此对于类似本例的简单问题,最好使用复制专用寄存器("0)。如果碰到需要将一段或多段文本粘贴到多处的情况,有名寄存器就会大显神通。

用小写字母引用有名寄存器,会覆盖该寄存器的原有内容,而换用大写字母的话,则会将新内容添加到该寄存器的原有内容之后。

4.1.4 黑洞寄存器

黑洞寄存器是个有去无回的地方,可用下划线("_)(参见:h quote_)引用它。如果我们运行"_d{motion}命令,Vim将删除该文本且不保存任何副本。当我们只想删除文本却不想覆盖无名寄存器中的内容时,此命令很管用。

4.1.5 表达式寄存器

Vim的寄存器通常被认为是保存一段文本的容器。然而,通过=号(参见:h quote=)引用的表达式寄存器("=)却是个例外。当我们从表达式寄存器获取内容时,Vim 将跳到命令行模式,并显示提示符=。这时,我们可以输入一段Vim脚本表达式并按<CR>执行,如果返回的是字符串(或者可被强制转换成字符串的数据),Vim 将会使用它。

4.1.6 系统剪贴板与选择专用寄存器

如果想从Vim复制文本到外部程序(反之亦然),则必须使用系统剪贴板。

Vim的加号寄存器与系统剪贴板等效,可用+号(参见:h quote+)引用。

如果我们在外部程序中用剪切或复制命令获取了文本,就可以通过"+p命令(或在插入模式下用<C-r>+)将其粘贴到Vim内部。相反地,如果在Vim的复制或删除命令之前加入"+,相应的文本将被捕获至系统剪贴板。这意味着我们能够轻松地把文本粘贴到其他应用程序中了。

X11 视窗系统支持另一种被叫作主剪贴板(primary)的剪贴板,它保存着上次被高亮选中的文本,可以用鼠标中键(如果有的话)把它们粘贴出来。Vim的星号寄存器对应主剪贴板,可用*号(参见:h quotestar)加以引用。

WindowsMac OS X操作系统并没有主剪贴板的概念,因此"+寄存器与"*寄存器可以混用 , 它们都代表系统剪贴板。

4.1.7 数字寄存器

10个数字寄存器 , 用0,1,…,9表示。

  • 0存放最近复制内容

  • 1存放最近删除内容。当新的文本变更和删除时,1转存到2,2转存到3,以此类推。

  • 数字寄存器不能在不同会话间共享

4.1.8 其他寄存器

我们可以显式地使用删除与复制命令,来设置有名、无名以及复制专用寄存器的 内容。另外,Vim还提供了几组可被隐式赋值的寄存器。它们被称作只读寄存器 ( 参见:h quote.),如下表所示:

寄存器

内容

"%

当前文件名

"#

轮换文件名

".

上次插入的文本

":

上次执行的 Ex 命令

"/

上次查找的模式

4.2 复制粘贴

Vim的删除、复制与粘贴命令都会用到众多寄存器中的某一个。我们可以通过给命令加"{register}前缀的方式指定要用的寄存器。若不指明,Vim将缺省使用无名寄存器

剪切(cut)、复制(copy)与粘贴(paste),这些都是众所周知的术语 , 而且大多数桌面软件和操作系统都支持这3类操作。Vim当然也提供这些功能 , 只不过使用的是另外的术语deleteyankput

Vimput命令与粘贴操作完全相同。幸运的是,两词均以字母p开头,因此即使术语不同,也不会影响记忆。

Vimyank命令也等同于复制操作。但由于历史原因,当时c命令已经被用于修改(change)操作了,因此Vi的作者们被迫选择了另一个名字yank。由于那时y键还可用,因此它就成了复制操作的命令。

Vimdelete命令也与标准剪切操作的作用一致。也就是说,该命令会先把指定文本复制到寄存器后再从文档中删掉。

如果我们想把当前单词复制到寄存器a中,可执行"ayiw,或者,可以用"bdd,把当前整行文本剪切至寄存器b中。在此之后,我们既可以输入"ap粘贴来自寄存器a的单词,也可使用"bp命令粘贴来自寄存器b的一整行文本 , 两者互不干扰。

除了普通模式的命令外,Vim也提供用于删除、复制与粘贴操作的Ex命令。例如 , 我们可以执行:delete c,把当前行剪切到寄存器c,然后再执行:put c命令将其粘贴至当前光标所在行之下。相比普通模式的命令而言,这些操作看似繁琐,但如果将它们与其他Ex命令结合起来使用,或者用于Vim脚本编程 , 将会更方便。

可视模式下使用p命令时,Vim将用我们指定的寄存器内容来替换高亮选区中的文本(参见:h v_p)。可视化模式中使用p,会直接用无名寄存器中的内容覆盖到选择好的区域中。

插入模式下,我们可以通过输入<C-r>"来插入无名寄存器的内容,或者输入<C-r>0来插入复制专用寄存器的内容

4.3 宏

宏很适合针对一系列相似的行、段落 , 甚至是文件 , 进行重复性的修改。我们将 会发现在一组目标上执行宏可以有两种方式—以串行方式回放或者以并行方式多次 运行。

在录制命令序列的过程中难免会出错 , 不过用不着推翻重来 , 因为可以很方便地在原有宏的结尾附加新的命令。如果要对宏进行较大的修改 , 我们甚至可以先将宏粘贴到一个文档里 , 对该命令序列进行编辑 , 然后再将其复制回寄存器。

宏允许我们把一段修改序列录制下来,用于之后的回放。q键既是录制按钮,也是停止按钮。为了录制我们的按键操作,一开始需要按q{register},从而指定一个用于保存宏的寄存器。当状态栏中出现记录中时,表示录制已经开始。此后,我们执行的每一条命令都将被宏捕获 , 直到我们再次按下q键停下为止。

vim-qreg

首先,我们输入qa开始录制宏并将其内容保存至寄存器a中,然后,在第一行上做两处修改,在行尾添加一个分号,再在行首添加一个单词var。在完成这些修改后,按q键停止宏的录制。在我们输入qa时,Vim将开始录制接下来的按键操作,并将它们保存到寄存器a中,这会覆盖该寄存器原有的内容。如果我们输入的是qA的话,Vim也会录制按键操作,但会把它们附加到寄存器a原有的内容之后。我们可以用这种方式更正该错误 :

我们可以通过以下命令查看寄存器a中的内容:

:reg a
--- Registers ---
"a   A;^[Ivar ^[
Press ENTER or type command to continue

我们可以用@{register}命令执行指定寄存器的内容(参见:h @),也可以用@@来重复最近调用过的宏。

vim-qreg-2

通过执行这个刚刚录制好的宏,Vim 对随后的每一行也重复了这两处相同的修改。

以串行方式执行宏 : 录制宏的过程很像是为机器人编写“完成一道工序”的程序。作为最后一步,我们命令机器人移动传送带并抓住下一个零件。按照这种方式,只需一个机器人就能在相似的零件上执行一系列重复的工序了。采用此法的后果之一,是如果机器人遇到了特殊情况,必须发出警报并中止操作。即使传送带上仍有零件需要装配,也只能放弃。

以并行方式执行宏 : 以并行方式执行宏,就好像完全不用传送带,取而代之的,是部署一组机器人。我们同样要控制它们完成刚才那个简单的任务。这一次,我们安排一个机器人只干一种工作。如果某个机器人能完成交给它的任务固然很好,但若失败了,也不会影响其他机器人。

当我们执行一个宏时,Vim会机械地重复这个打包在一起的按键操作序列。如果我们不小心的话,在回放宏时的结果会偏离我们的预期。但我们也可以录制更灵活的宏,针对每一种情况,它都能应对自如。

黄金法则 : 在录制一个宏时,要确保每条命令都可被重复执行。

  • 规范光标的位置 一旦你开始录制宏,首先要问自己几个问题,我在哪里?我从哪里来?我要去哪里?在你做任何事之前,要确保你的光标位置已经就位,只有这样,下一条命令才会做你想做的事情 , 去你想去的地方。

    这也许意味着应该把光标移到下一处查找匹配项(n),或者当前行的行首(0),又或是当前文件的首行(gg)。如果每次总是从确定的位置开始执行的话,那么命中正确的目标会变得更容易。

  • 用可重复的动作命令直达目标 面向单词的动作命令,如wbege,与面向字符的动作命令hl相比,更具灵活性。如果我们录制“动作命令0,后跟e”,当每次执行该宏时,我们都能预料得到一致的结果,光标会移到当前行第一个词的最后一个字符上。只要该行包含至少一个词,无论该词包含多少个字符,都能够到达目标。

    我推荐你用查找命令定位,或者用文本对象。总之,请用好Vim提供的所有动作命令,尽量使你的宏兼具灵活性与可重复性。还有一点别忘了,在录制宏的过程中,禁止使用鼠标。

  • 当动作命令失败时 , 宏将中止执行 Vim的动作命令可能会执行失败。举例来说,如果光标位于文件的首行,运行 k命令将什么也不会发生。若光标位于文件的末行,按下j也会出现同样的情况。当发生上述情况时,Vim缺省会发出“哔”的一声,提示我们动作命令失败了。当然,也可以设置visualbell(参见:h 'visualbell')关闭提示音。

    如果宏执行动作命令失败了,Vim将中止执行宏的其余命令。这是一项功能,而不是漏洞。我们可以用动作命令进行简单测试,来判断该宏是否应该在当前上下文中继续执行。

    考虑这样一个例子 : 我们要查找一个模式(pattern),假设文档中有10处匹配。我们开始录制宏,先用n重复上一次的查找操作。一旦光标移到匹配处,我们会做一些小的修改。然后停止宏的录制。改完这处后,这个地方就再没有可匹配的模式了。至此 , 整篇文档只剩 9 处匹配了。

    当我们执行这个宏时 , 光标将移到下一处匹配并做相同的修改。至此,整篇文档只剩下8处匹配了。就这样,我们周而复始地执行该宏,直到再也没有一处匹配为止。若此时再执行该宏,由于已没有匹配项,n命令会失败,宏将中止退出。运行多次宏时,可以直接在宏前加上次数,如n@a;100@a这样

如果需要在多行执行宏时,可以通过串行或者并行的方式执行。如果时串行,可以直接多次执行宏,或者在宏前加上数值。 先使用以下方式建立一个宏值

串行方式 : 我们可以用@a执行刚刚录制好的宏 , 它将执行以下步骤 : 首先 , 把光标移到该行首个.字符上,把.改成),然后,将下一个单词的首字母变为大写,最后,将光标移至下一行。 我们可以调用3@a命令完成这次任务,但运行3@a会更快:

并行执行 : 我们又重新录制了宏。这一次,除了省略向下移动的j命令(最后一条)外 , 宏命令没变,因为这一次,我们不再需要它移动光标至下一行了。通过视图的方式在多行中同时进行宏操作。

如果宏在每次执行时都能插入一个可变的数值,这将会很有用处。我们只需在插入模式下运行<C-r>=i<CR>,即可插入变量i 的值。

之后通过并行的方式执行宏命令即可。

5. 模式

5.1 匹配

5.1.1 大小写敏感

我们既可以全局性地调整Vim查找功能的大小写敏感性,也可以在每次查找时进行局部调整。

  • 全局设置 在vim中有两个参数可以设置大小写匹配问题。ignorecasesmartcase这两个参数。

    如果启用ignorecase设置,Vim的查找模式将不区分大小写。

    如果启用smartcase选项。无论何时,只要我们在查找模式中输入了大写字母,ignorecase设置就不再生效了。换句话说,如果我们的模式全是由小写字母组成的,就会按照忽略大小写的方式进行查找,但只要我们输入一个大写字母 , 查找方式就会变成区分大小写的了。因此这个参数是基于igorecase参数,需要开启ignorecase参数之后smartcase才能生效

  • 局部设置 通过使用元字符\c\C,可以覆盖Vim缺省的大小写敏感性设置。小写字母\c会让查找模式忽略大小写,而大写字母\C则会强制区分大小写。若在某个查找模式中使用了两者中的某一个,ignorecase的值将被这次查找忽略。

    注意 : 这两个元字符可以出现在模式的任意位置。假设你已输入了完整的模式,却发现需要按区分大小写的方式进行查找。此时,你只需在模式的结尾加上\C,该元字符就会作用于它前面的所有文本。

5.1.2 magic

vim中有个magic的设定。设定方法为:

:set magic  设置magic 
:set nomagic  取消magic 
:h magic  查看帮助

vim毕竟是个编辑器,正则表达式中包含的大量元字符如果原封不动地引用(像perl 那样),势必会给不懂正则表达式的人造成麻烦,比如/foo(1)命令,大多数人都用它来查找foo(1)这个字符串,但如果按照正则表达式来解释,被查找的对象就成了foo1了。

于是 , vim就规定 , 正则表达式的元字符必须用反斜杠进行转义才行,如上面的例子,如果确实要用正则表达式,就应当写成/foo\(1\)。但是,像.,*这种极其常用的元字符,都加上反斜杠就太麻烦了。而且,众口难调,有些人喜欢用正则表达式,有些人不喜欢用……。

为了解决这个问题,vim设置了magic这个东西。简单地说,magic就是设置哪些元字符要加反斜杠哪些不用加的。 简单来说:

magic (\m) : 除了$ . * ^ 之外其他元字符都要加反斜杠。
nomagic (\M) : 除了 $ ^ 之外其他元字符都要加反斜杠。

这个设置也可以在正则表达式中通过\m\M开关临时切换。\m后面的正则表达式会按照 magic处理,\M后面的正则表达式按照nomagic处理,而忽略实际的magic设置。具体可以使用命令:h magic来查找具体的帮助手册

示例:

/\m.* # 查找任意字符串 
/\M.* # 查找字符串 .*  ( 点号后面跟个星号 ) 

另外还有更强大的\v\V

\v(即very magic之意) : 任何元字符都不用加反斜杠。 \V(即very nomagic之意) : 任何元字符都必须加反斜杠。

注: /?分别作为正向查找(/)和反向查找(?)的域结束符。在任何模式中当查找的内容中包含这两个符号时需要进行转义。同时\不管在什么模式中都需要进行转移。

示例:

/\v(a.c){3}$ # 查找行尾的abcaccadc 
/\m(a.c){3}$ # 查找行尾的(abc){3} 
/\M(a.c){3}$ # 查找行尾的(a.c){3} 
/\V(a.c){3}$ # 查找任意位置的(a.c){3}$
  • 量词 本文下面使用的元字符都是magic模式(除了$,.,*,^之外其他元字符都要加反斜杠)下的,在very magic模式下,只需要将\去掉即可。

    vim

    意义

    *

    匹配0个或多个(匹配优先)

    \+

    匹配1个或多个(匹配优先)

    \?\=

    0个或1个(匹配优先) , \?不能在?命令(逆向查找)中使用

    \{n,m}

    匹配n个到m个(匹配优先),如\d{1, 3}可以匹配13个数字,类似11,1,333

    \{n,}

    最少n个(匹配优先)

    \{,m}

    最多m个(匹配优先)

    \{n}

    恰好n

    在这里,有些东西需要说明一下,那就是上面的用于限定数量的元字符不单单可以用于字符,同时也可以用于模式,举个例子,下面的模式:

    \(123\)\{2}

    可以匹配123123.

  • 常用元字符

    元字符

    说明

    .

    匹配任意一个字符,如p.p可以匹配字符串pep,pip或者pcp

    [abc]

    匹配方括号中的任意一个字符。可以使用-表示字符范围

    [a-z0-9]

    匹配小写字母和阿拉伯数字

    [^abc]

    在方括号内开头使用^符号 , 表示匹配除方括号中字符之外的任意字符

    \d

    匹配阿拉伯数字,等同于[0-9]

    \D

    匹配阿拉伯数字之外的任意字符,等同于[^0-9]

    \x

    匹配十六进制数字,等同于[0-9A-Fa-f]

    \w

    匹配单词字母 , 等同于[0-9A-Za-z_]

    \W

    匹配单词字母之外的任意字符 , 等同于[^0-9A-Za-z_]

    \t

    匹配<TAB>字符

    \s

    匹配空白字符,等同于[ \t]。匹配<Space>and<Tab>

    \S

    匹配非空白字符,等同于[^ \t]

    \a

    所有的字母字符. 等同于[a-zA-Z]

    \l

    小写字母[a-z]

    \L

    非小写字母[^a-z]

    \u

    大写字母[A-Z]

    \U

    非大写字幕[^A-Z]

5.1.3 圆括号捕获子匹配

当我们指定一个模式时,可以捕获其子匹配,并在其他地方引用它们。此功能与substitute 命令组合起来尤为好用,但它也可用于定义某一类模式,这类模式的特点是重复包含某个单词

这有一个专门用来匹配重复单词的正则表达式 :

\v<(\w+)\_s+\1>

\v模式开关会激活very magic搜索模式。另外,<>两符号将用于匹配单词的边界。最后,元字符\_s会匹配空白符或换行符(分别参见:h /\_:h 27.8)

我们之所以能两次匹配相同的单词,诀窍就在于()\1的组合使用。任何圆括号内部的匹配文本都会被自动保存到一个临时的仓库。我们可以用\1引用这段被捕获的文本。如果模式中包含不止一组圆括号 , 则可以用\1\2,直到\9,引用被每对()捕获的子匹配。另外,不论模式中是否使用了圆括号,元字符\0永远会引用整个匹配。

5.1.4 匹配边界

一个匹配的边界通常对应于一个模式的起始与结尾。但我们可以使用元字符\zs\ze对匹配进行裁剪 , 使其成为这个完整模式的一个子集(参见:h /\zs)。元字符\zs标志着一个匹配的起始,而元字符\ze则用来界定匹配的结束。将二者相结合,我们可以定义一个特殊的模式,它们可以让我们定义一个模式匹配一个较大的文本范围,然后再收窄匹配范围。

用一个示例可以帮助大家理解这一点。如果我们查找/Practical Vim<CR>,文档中所有出现Practical Vim的地方都会被高亮起来。一旦将查找模式改为/Practical \zsVim<CR>,则只有单词Vim会被高亮,而单词Practical会被排除于匹配之外,但它仍是模式的一部分。这样一来 , 只有紧跟着单词PracticalVim才会被高亮,而其他前面不是PracticalVim则不会被匹配。这与通过/Vim<CR>命令进行简单查找的结果有很大不同。

还有另外一个例子,这一次,我们同时使用\zs\ze 对匹配的起始与结尾进行微调。

5.2 查找

在普通模式下,按下/键会调出Vim的查找提示符,可在它的后面输入要查找的模式或者原义文本。另外,只有当我们按下<CR>键时,Vim才会执行查找命令,而如果换用<Esc>键的话,查找提示符会消失,我们将重回普通模式。

当我们执行一次查找时,Vim会从当前光标位置开始向下扫描,停在所找到的第一处匹配上。如果Vim 扫描到文档结尾仍未找到目标,会提示“已查找到文件结尾,再从开头继续查找”,这意味着在某些情况下,一次正向查找反而把我们带到了查找起点之前。尽管听上去有点让人困惑,但只要记住查找命令抵达文档结尾处时会回绕至文档开头继续查找,这就说得通了。

如果你只想在当前光标位置至文档结尾的范围内查找,而不想绕回文档继续查找的话,可以关闭wrapscan选项。(参见:h 'wrapscan')。

指定查找方向 : 当我们使用/键执行一次查找时,Vim将进行正向扫描。而如果是用?键调出查找提示符的话,Vim则会进行反向查找。查找提示符始终以/或者?字符开头,表明此次查找的扫描方向。

重复上一次查找 : n命令用于跳转到下一处匹配,而N命令则用于跳转到上一处匹配。通过nN这两条命令,我们可以方便地在各个匹配之间往来穿梭。但下一处匹配的定义取决于上下文环境。

n命令既会保证查找方向不变,又会使查找的偏移与上一次保持一致。因此,如果我们用/ 执行一次正向查找,n将继续向下查找;而如果最初的查找命令是?的话,n将继续向上查找。同时,N命令将始终与上一次查找的方向保持相反。

命令

用途

n

跳至下一处匹配 , 保持查找方向与偏移不变

N

跳至上一处匹配 , 保持查找方向与偏移不变

/<CR>

正向跳转至相同模式的下一处匹配

?<CR>

反向跳转至相同模式的上一处匹配

查找命令允许我们在诸多匹配中快速跳转,但在缺省情况下,Vim不会将这些匹配可视化地凸现出来。通过启用hlsearch选项.无论是在当前文档的编辑窗口还是在其他已打开的分割窗 口中,所有匹配都将被高亮起来。可以运行:set nohlsearch彻底禁用该功能。

在查找命令之后可以添加动作类命令,保证查找过程中每次光标可以跳转到指定位置。

我们使用/lang/e<CR>进行查找,该命令会像我们期望的那样,将光标置于查找匹配的结尾。这样一来,每当我们使用n命令时,光标都会被定位于下一处查找匹配的结尾,让我们可以完美地使用.命令。可以说,查找偏移功能让我们得到了理想的点范式。

假设我们一开始并没有用偏移执行查找命令,而是在敲了若干次n 之后,才发现最好将光标移至匹配的结尾。亡羊补牢,犹未为晚。我们可以简单地运行//e<CR>。当我们把查找域留空时,Vim将重用上一次的查找模式,因此,该命令将使用偏移重复上一次查找。

在普通模式中输入*,会在全文中查找光标所在单词。

5.3 替换

:substitute命令很复杂,除了要提供查找的模式以及替换字符串外,还要指定执行的范围。另外,作为可选项,我们还可以通过标志位来调整该命令的行为。

substitute命令允许我们先查找一段文本,再用另一段文本将其替换掉。命令的语法如下所示:

:[range]s[ubstitute]/{pattern}/{string}/[flags]

一条完整的substitute命令由许多部分组成。其中,[range]的规则对于每一条Ex 命令都适用,substitute命令也不例外。{pattern}的用法,也是之前5.1章节中提及过。

我们可以利用标志位来调整substitute命令的行为。要充分了解substitute标志位的作用,最佳的途径就是在实际应用中对其进行观察。因此,让我们简短地将其他技巧中用到的标志位在此处做一番总结。(关于完整的参考资料,请查询:h s_flags 。)

标志位g : 使得subsititute命令可在全局范围内执行,即可以修改一行内的所有匹配,而不仅仅是第一处匹配。在缺省情况下,substitute命令仅仅作用于当前行,而且只会修改第一处匹配。因此,为了在整个文件的范围内修改每一处匹配,我们必须指定范围,并使用标志位g

标志位c : 让我们有机会可以确认或拒绝每一处修改。引入标志位c后,Vim会对每处匹配结果提示“替换为copy ?”,我们可以按y键,完成这次修改,或者按n键,跳过这一次修改。无论采用哪种方式,Vim 都会执行我们的决定,并移到下一匹配处再次提示。下表中便是可以回答的答案列表。

答案

用途

y

替换此处匹配

n

忽略此处匹配

q

退出替换过程

l

last”—— 替换此处匹配后退出

a

all” —— 替换此处与之后所有的匹配

<C-e>

向上滚动屏幕

<C-y>

向下滚动屏幕

标志位n : 会抑制正常的替换行为 , 即让Vim不执行替换操作,而只是报告本次substitute命令匹配的个数。

当我们执行substitute命令时,如果在当前文件中没有匹配到该模式,Vim会提示错误信息“E486: 找不到模式”。标志位e专门用于屏蔽这些错误提示。

标志位i : 可以忽略替换字母的大小写

标志位& : 仅仅用于指示Vim 重用上一次substitute 命令所用过的标志位。

执行substitute命令通常包括两个步骤:一是撰写查找模式,二是设计合适的替换字符串。因此,一分为二的技术让我们消除了这两项任务的耦合性,这才是关键所在。

在我们撰写复杂的正则表达式过程中,通常需要尝试多次才能达到正确的匹配效果。如果打算通过执行substitute命令的方式来验证模式的话,每次执行命令都会改变文档的内容,这样做简直太麻烦了。与之形成鲜明对比的是,当执行查找命令时,文档不会被修改。因此,即使我们犯的错误再多也无所谓。可以先运行查找命令,当找到对应的查找命令之后。总之,将两个任务彻底分开,将使得我们的工作过程更加清晰。

因此substitute中以下命令

:%s/\v'(([^']|'\w)+)'/“\1”/g

它等价于以下两条单独的命令:

/\v'(([^']|'\w)+)'
%s//“\1”/g\

我们已经发现一些字符在用作查找模式时具有特殊含义。替换域中也有一些特殊字符。

下表只是总结了其中的一部分常用符号:

符号

描述

\r

插入一个换行符

\t

插入一个制表符

\\

插入一个反斜杠

\1

插入第1个子匹配

\2

插入第2个子匹配 ( 以此类推 , 最多到\9 )

\0

插入匹配模式的所有内容

&

插入匹配模式的所有内容

~

使用上一次调用:substitute 时的{string}

\={Vim script}

执行{Vim Script} 表达式;并将返回的结果作为替换{string}

当我们在文档中任意选中一段文本后 , 该映射项允许我们按*键来查找这段文本。接下来 , 我们就可以用一条查找域为空的substitute 命令,把选中的内容(以及其他匹配)替换掉了。

假设我们刚刚执行完以下命令 ( 其作用范围为当前行 ) :

s/target/replacement/g

突然,我们意识到了失误,应该加上前缀% 才对。幸好该命令没有造成什么不良后果。

接下来 , 我们只需输入g&(参见:h g&),即可在整个文件的范围内重复这条命令。在效果上,它等同于以下命令:

%s//~/&

这条命令可以详解为如下指令:用同样的标志位、同样的替换字符串、同样的查找模式以及新的执行范围%,重复上一次substitute命令。换句话说,该命令表示在整个文件的范围内重复上一次替换操作。

另外,我们需要解释一下:&& 命令,因为这两处&符号的含义有所不同。前一个&作为Ex 命令:&的组成部分,用作重复上一次的:substitute命令(参见:h :&),而第二个&则会重用上一次:s命令的标志位。

在替换过程中可以使用除了/其他的分隔符。只需要将对应符号放在分隔符的位置即可 s@/etc@/var@g s#/boot#/#i

6. global

:global命令允许我们在某个指定模式的所有匹配行上运行Ex 命令

:global命令通常采用以下形式:

:[range] global[!] /{pattern}/ [cmd]

首先,在缺省情况下,:global命令的作用范围是整个文件(%),这一点与其他大多数Ex 命令(包括:delete:substitute以及:normal)有所不同,这些命令的缺省范围仅为当前行(.)。

其次,{pattern}域与查找历史相互关联。这意味着如果将该域留空的话,Vim会自动使用当前的查找模式。

[cmd]可以是除:global命令之外的任何Ex 命令。


参考链接:
vim编辑器:博客园:少年阿斌

vim实用技巧:Drew Neil著,杨源 车文隆译:人民邮电出版社:2014.5

VIM编辑器学习札记--Ctrl+O :新浪博客:xshuai1006的博客

Vim编辑器:linuxprobe vim正则表达式:简书:Yihulee


熊熊