Series · Linux · Chapter 9

Linux Vim 解析

用『学语法』而不是『背快捷键』的方式入门 Vim:模式、操作符 + 动作、文本对象、宏、寄存器、最小 .vimrc 与一周练习计划。

很多人放弃 Vim,是因为他们试图把所有快捷键都背下来。这条路一开始就走错了。Vim 本质上是一门很小的语言:只要掌握它的语法——操作符 + 动作——你就能临场表达出任何编辑操作,再也不用翻速查表。本文会把每天会用到的那 80% 讲透,再说明剩下 20% 是怎么从同一套规则里自然『拼』出来的。

这篇文章会覆盖什么

  • 唯一的核心思想:模式 + 可组合的操作(操作符 + 动作)
  • 那少数几个能解决 90% 编辑场景的动作、文本对象、操作符
  • 文件操作、查找替换、宏、标记、寄存器
  • 缓冲区 / 窗口 / 标签页之间的关系——初学者最容易搞混的概念
  • 一份最小可用的 .vimrc,加上一份一周的刻意练习计划

写在前面

  • 任何终端都行(Vim 在几乎所有类 Unix 系统上都预装好了)
  • 愿意接受『头一周比平时慢』的事实

一、核心思想:模式 + 一套微型语法

Vim 的四种模式

Vim 的整个使用过程其实就是一个循环:

  1. 移动到要修改的位置(一个动作)。
  2. 选择作用范围(隐含在动作里,或者用可视模式显式选中)。
  3. 执行一个操作符——删除、修改、复制。

把这个循环养成肌肉记忆之后,你就不再是『记按键』,而是在『讲 Vim』了。

真正会用到的四种模式

  • 普通模式(Normal):导航和命令的入口。Vim 启动时默认就在这里,你大概有 80% 的时间应该待在这里。
  • 插入模式(Insert):输入文本。把它当成『临时绕路』即可:进去、敲一段、出来。
  • 可视模式(Visual):选中一段文本(按字符 / 按行 / 按矩形块)。
  • 命令行模式(Command-line):所有以 : 开头的命令(保存、查找替换、设置),以及 /? 开头的搜索。

还有个 R 替换模式,但大部分人都是不小心进去的。如果你打字时发现是在『覆盖』而不是在『插入』,按 Esc 就回来了。

最重要的一个习惯:敲完字立刻按 Esc。空闲时一定要待在普通模式,因为只有在普通模式下,每个键才是一条命令。

操作符与动作——Vim 的语法

一条 Vim 命令读起来就像一句很短的英语:

[次数] 操作符 动作

操作符——做什么

含义
d删除(delete)
c修改(change,删完进入插入)
y复制(yank)
>向右缩进
<向左缩进
=自动缩进

动作 / 文本对象——作用多大

  • w / b:跳到下 / 上一个单词
  • 0 / ^ / $:行首 / 第一个非空字符 / 行尾
  • gg / G / {n}G:文件开头 / 末尾 / 第 n
  • iw / ip / i":当前单词 / 段落 / 引号内的内容

接下来开始组合:

  • dw:删一个单词
  • d$:删到行尾
  • ciw:把当前单词整个改写
  • yip:复制整个段落
  • 3dw:删后面三个单词

整套游戏就这样。把那五个操作符记住、再把十来个动作练熟,你就已经比绝大多数图形编辑器更快了。


二、移动:用『跳』而不是『爬』

Vim 移动键速查

上面这张图建议打印一次,然后永远不要再翻第二次。真正要记住的不是按键,而是这几个类别

  • 字符级h j k l):实在没有更好的办法时才用。如果你按 j 超过三下,说明应该换一种动作。
  • 单词级w b e):按词跳。
  • 行级0 ^ $):行首、第一个非空字符、行尾。
  • 文件级gg G {n}G):文件开头、末尾、指定行。
  • 屏幕级Ctrl-fCtrl-bCtrl-dCtrl-u):翻整页 / 翻半页。
  • 搜索级/pattern?patternnN*#):在代码里最快的导航方式从来不是滚动,而是直接搜你要找的那个标识符。

有两个动作值得专门说一下,因为它们和操作符配合得特别好:

  • f{字符} 跳到当前行下一个该字符的位置;t{字符} 则跳到它的前一格。所以 df, 就是『删到下一个逗号(含逗号)为止』——改函数参数列表时几乎天天用。
  • % 在配对的括号之间跳。光标停在 ( 上按 d%,整段括号表达式连同括号一起删掉。

三、编辑:删 / 改 / 复制 / 粘贴

删除

按键效果
x删光标下的字符
dd删整行
d{动作}删动作覆盖到的范围
D从光标删到行尾(等同于 d$

修改(删 + 进入插入模式)

按键效果
cc整行重写
c{动作}替换动作范围内的内容并开始输入
C从光标改到行尾(等同于 c$

复制(yank)

按键效果
yy复制整行
y{动作}复制动作覆盖到的范围
Y复制到行尾

粘贴

  • p:粘到光标后面(行复制时是粘到下一行)。
  • P:粘到光标前面

撤销与重做

  • u:撤销。
  • Ctrl-r:重做。
  • :earlier 5m / :later 30s:在撤销里按时间穿梭(没错,Vim 的撤销是一棵树,不是一个栈)。

没有专门的『剪切』

Vim 里删除本身就把内容放进了寄存器,所以剪切就是『删 + 粘』:dd → 移到目标位置 → p

那个神奇的『.』

. 重复你上一次的修改。比如先 ciw、敲入 host、按 Esc,再把光标移到下一个单词上按 .,那个单词也会被改成 host。这个『重复点』把『操作符 + 动作』的语法变成了一个一键宏;养成『让每次编辑都可以被点一下重复』的习惯非常划算。


四、文本对象:按『语义』而不是按『位置』编辑

文本对象示意

文本对象是初学者最容易跳过、老手却几乎每分钟都在用的特性。它的好处是:你不用手动去定位边界,而是直接说出一个语义单元

  • i(inner):包含边界本身。
  • a(around):包含边界本身。
对象含义例子
iw / aw单词内 / 含周围ciw 改一个单词
is / as句子内 / 含周围dis 删一句
ip / ap段落内 / 含周围yip 复制一段
i" / a"双引号内 / 含引号ci" 重写字符串字面量
i' / a'单引号内 / 含引号di' 删一段单引号字符串
i( / a(括号内 / 含括号da( 连括号一起删
i{ / a{花括号内 / 含花括号ci{ 重写一个代码块
i[ / a[方括号内 / 含方括号di[ 删一个数组字面量
it / atHTML 标签内 / 含标签cit 重写标签内容

光标不需要精确停在边界上——ci" 在引号内任何位置(甚至光标停在引号本身上)都能用。这正是它的精髓:思路从『先选后改』变成了『就改这个东西』。


五、查找与替换:要快,更要稳

/pattern 向后搜,?pattern 向前搜;nN 分别跳到下一个 / 上一个匹配。

替换沿用经典的 ed/sed 语法:

:s/old/new/        " 当前行第一个匹配
:s/old/new/g       " 当前行所有匹配
:%s/old/new/g      " 整个文件所有匹配
:10,40s/old/new/g  " 只在第 10 到 40 行替换
:%s/old/new/gc     " 整个文件,每个匹配都问一下

比直接 :%s/old/new/g 更稳的工作流:

  1. /old,按 n 走一遍,确认匹配的就是你想要的。
  2. :%s//new/gc——空模式会复用上一次的搜索,加上 c Vim 会对每个匹配问你 y / n / a / q
  3. 完事后 :noh 把高亮清掉。

模式里几个常用标志:

  • \c 不区分大小写,\C 强制区分大小写
  • \<word\> 全词匹配
  • \v 启用 very magic 模式,让更多字符按正则元字符解释(更接近 Perl/PCRE 的写法)

六、宏:在 Vim 里就把重复活儿干掉

宏可能是 Vim『进阶』特性里性价比最高的一个,因为它能把任何一次重复操作变成『录一次、回放 N 次』。

qa            " 开始录制到寄存器 a
... 操作 ...  " 做你想重复的那次完整编辑
q             " 停止录制
@a            " 回放
@@            " 回放上一次用过的宏
20@a          " 重复 20 次

写出鲁棒的宏有一个小窍门:让每次循环都从一个确定的起点出发。常见模板是:每次开头按 0(回到行首),结尾按 j(下移一行)。这样在连续的若干行上回放时,光标位置永远不会错位。


七、寄存器:一个有三十多个槽位的剪贴板

每一次复制和删除,内容都会进入某个寄存器。Vim 的寄存器不止一个:

  • "":默认无名寄存器(最近一次删 / 复制的内容)。
  • "a"z:26 个具名寄存器,由你自己往里放。
  • "0"9:复制 / 删除的历史记录。
  • "+"*:系统剪贴板(需要 Vim 编译时带 +clipboard,可以 vim --version | grep clipboard 检查)。
  • "_:黑洞寄存器。扔进去的内容凭空消失,特别适合『我要删除但不想覆盖剪贴板里那段』的场景。

用法举例:

"ayy   " 把当前行复制到寄存器 a
"ap    " 从寄存器 a 粘贴
"+yy   " 复制到系统剪贴板
"+p    " 从系统剪贴板粘贴
:reg   " 看一眼所有寄存器现在装了啥

老手的小习惯:当你要『先粘贴某段,再删别的东西』时,把后面的删除写成 "_dd,这样剪贴板里那段不会被覆盖,可以反复粘。


八、标记:在文件里 / 工程里设书签

ma     " 在当前位置打一个标记 a
'a     " 跳到标记 a 所在的行
`a     " 跳到标记 a 的精确位置

小写标记(a-z)只在当前文件内有效;大写标记(A-Z)是全局的,跨文件也能用——『我先放个书签待会儿回来看』时特别顺手。

Vim 还会自动维护几个特殊标记:

  • ` `:上一次跳转之前的位置。
  • `.:上一次编辑的位置。
  • `^:上一次进入插入模式的位置。
  • `":上次关闭这个文件时光标的位置。

九、可视模式——尤其是『可视块』

三种模式:

  • v:按字符选。
  • V:按行选。
  • Ctrl-v:按矩形块选。这才是真正的杀手锏。

可视块解决了那个『要在二十行里改同一列』的问题——在大多数其他编辑器里这事得动正则或者宏:

给 5 行加注释:     Ctrl-v  jjjj  I  #  Esc
给 10 行末尾加分号: Ctrl-v  9j    A  ;  Esc
删掉一列共 4 行:   Ctrl-v  jjj   d

套路是:进块模式,纵向拉选区,然后按 I(在块前插入)或 A(在块后追加),输入一次,按 Esc,Vim 会把这次输入应用到选区里每一行


十、文件、窗口、分屏与标签页

文件操作(命令行模式)

命令效果
:w保存
:q退出
:wq / :x / ZZ保存并退出
:q!放弃修改强制退出
:e {文件}打开文件
:saveas {文件}另存为
:!命令调用 shell(如 :!ls:!git status

分屏

命令效果
:sp {文件}水平分屏
:vsp {文件}垂直分屏
Ctrl-w h/j/k/l在分屏之间跳
Ctrl-w =把所有分屏调成等宽 / 等高
:close / :only关闭当前分屏 / 只保留当前分屏

缓冲区、窗口、标签页

这是初学者最容易搞混的三个概念,慢慢看一遍就清楚了:

概念是什么相关命令
缓冲区 Buffer已经载入内存的一个文件:ls:b {名字}:bnext:bprev
窗口 Window显示某个缓冲区的一个『视口』:split:vsplitCtrl-w 系列
标签页 Tab一组窗口的布局:tabnewgtgT

打开一个文件的瞬间它就是一个缓冲区——哪怕暂时没有任何窗口在显示它。两个窗口可以同时显示同一个缓冲区(看长文件的开头和结尾时很有用)。标签页不是浏览器那种『打开的文件标签』,而是保存的窗口布局

.swp 交换文件

如果你打开的文件 Vim 在别处已经在编辑,或者上次 Vim 是崩溃退出的,会看到一段关于 .swp 的警告。Vim 是在问你:要不要从崩溃的 .swp恢复未保存的修改,还是当无事发生继续编辑。如果不需要恢复,删掉孤立的 .swp 就不会再提示:

find . -name ".*.swp" -delete

十一、一份最小、安全的 .vimrc

Vim 把东西放在哪里

设置好少数几条默认值,Vim 才会用着舒服。先不要急着装插件——从下面这份开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
" ---- 显示 ----
set number
set relativenumber
set cursorline
syntax on

" ---- 缩进 ----
set expandtab
set tabstop=4
set shiftwidth=4

" ---- 搜索 ----
set ignorecase
set smartcase     " 输入有大写时才区分大小写
set hlsearch
set incsearch

" ---- 易用性 ----
set hidden        " 切换 buffer 不强制要求保存
set scrolloff=4   " 光标周围保留 4 行上下文
set wildmenu      " 命令行补全菜单

" ---- 保存时去掉行尾空白 ----
autocmd BufWritePre * :%s/\s\+$//e

存到 ~/.vimrc 然后重启 Vim 就生效了——Vim 没有常驻进程,配置是启动时读一次。

如果用 Neovim,等价的配置文件在 ~/.config/nvim/init.vim(Lua 版本是 init.lua)。


十二、两个每天都会用到的工作流

两个最常用的工作流

掌握了基础后,下面这两条工作流会占掉你在 Vim 里大部分时间。图里把每一步都画出来了,下面也把同样的流程写成纯文本,方便你边练边复制:

整工程范围的查找替换

/oldName             " 先走一遍匹配,确认范围
:%s//newName/gc      " 空模式复用上一次搜索,c = 每个匹配都确认
:noh                 " 清除高亮残留

多文件同时编辑

:e src/api.py        " 打开文件 1
:vsp src/main.py     " 垂直分屏,左右同时看两个文件
:sp README.md        " 水平分屏,上下再加一个
Ctrl-w h/j/k/l       " 在窗口之间跳来跳去
:ls   :b api         " 列出所有 buffer,用名字片段切换

十三、几个常见坑,以及怎么爬出来

1. 粘进去的代码缩进越来越深。 Vim 的自动缩进会对每一行重新缩进。解决:

:set paste       " 关掉自动缩进
" ... 粘贴 ...
:set nopaste

更简单的办法是用 "+p 直接从系统剪贴板粘——Vim 把它当成一整块。

2. 突然变成『覆盖输入』了。 你按到了 R(或者键盘上的 Insert 键),进了替换模式。按 Esc 即可。

3. 每次打开都弹 .swp 警告。 上次的 Vim 没有正常退出。删掉孤立的 .swp(见第十节),或者在 Neovim 里跑一下 :checkhealth

4. u 撤销的不是你想的那一步。 Vim 的撤销是,不是栈。用 :earlier 5m / :later 30s 按时间穿梭。

5. 退不出 Vim。 经典问题。任何时候先按 Esc 回到普通模式,然后输入 :q! 回车(不保存退出),或者 :wq 回车(保存退出)。


十四、用 Vim 还是 Neovim?

两个都很好,按实际情况选:

  • 继续用 Vim:如果你大部分时间在远程服务器和容器里编辑(Vim 几乎到处都预装着),或者你不需要 LSP / 现代插件生态。
  • 换 Neovim:如果你想要内置的 LSP(自动补全、跳转定义、诊断),喜欢用 Lua 写配置,想要异步插件,想跟上更活跃的社区。

两者来回切几乎没有学习成本——编辑器命令完全一样,只是配置文件和插件层不同。


十五、一份一周刻意练习计划

『流利使用 Vim』和学外语其实是一个套路:在固定的一段时间里强迫自己用它,哪怕一开始比平时慢。

Day 1-2 :只允许用这些。 hjklwb0^$dddwciw/:%s。所有编辑都用这套来做,会非常慢——这就是关键。

Day 3-4 :加上文本对象。 强制自己用 ciwci"ci(dap,而不是手动选。这一步开始你会感觉到 Vim 真正的『快』。

Day 5 :加上宏。 一旦发现某个操作做了第三遍,就录一个宏:qaq@a

Day 6 :加上分屏和缓冲区。 不要再开一堆终端标签了,用 :vsp:sp:bCtrl-w 来切换。

Day 7 :开始定制。 这时候才去碰 .vimrc:把第十一节那一份贴进去,再加一两个自己的映射。至少再过一周才考虑装插件。

熬过这一周,整套语法就在你手指上了。之后每学到一个新动作或新文本对象,都会自然地『嵌入』到你已经会的语法里——再也不存在『要不要单独记一下』这种事。


总结:十条原则

  1. 模式是你的朋友。 普通模式是家,插入模式是临时绕路。
  2. 思考方式是『操作符 + 动作』。 d + w = 删一个词。所有命令都是一句话。
  3. 用文本对象。 ciwdi"yip 永远比手动选范围快。
  4. 先搜再改。 /foo 然后 cw,比一直滚屏快得多。
  5. . 重复。 简单编辑里,. 就是一键宏。
  6. 复杂重复就录宏。 qaq20@a
  7. 少数动作练熟。 w/b0/$gg/Gf{字符}/% 已经覆盖大部分导航。
  8. 不要急着定制。 先学默认行为,再加映射,最后才考虑插件。
  9. 用 buffer 和分屏,而不是开一堆终端窗口。
  10. 刻意练习一周。 之后 Vim 就不再是『难用』,而是『顺手』。

当这套语法长在手指上之后,Vim 就不再是一个你『操作』的工具,而是一门你会说的文本编辑语言。


接下来可以看

  • vimtutor —— 在终端里直接运行,30 分钟交互式入门,Vim 自带。
  • 《Practical Vim》(Drew Neil 著)—— 从入门到熟练最好的一本书。
  • Vim Adventures —— 用游戏的方式练习 Vim 动作。
  • Vim Golf —— 比谁用最少按键完成同一个编辑任务,进阶玩家的乐园。

Liked this piece?

Follow on GitHub for the next one — usually one a week.

GitHub