为什么要学这一部分
平时把 Vim 当作编辑器使用时,很容易形成这样的习惯:退出 Vim,执行一条 Shell 命令,再重新打开文件。实际上,Vim 和 Shell 之间有多种协作方式。学会后,可以在不丢失当前缓冲区、光标位置、窗口布局和撤销历史的情况下,临时回到 Shell,或者直接让外部命令处理当前文本。
这一部分最值得先掌握的不是大量命令,而是三个层次:
- 暂时离开 Vim,再回到原来的编辑现场;
- 在 Vim 内执行一条或一组 Shell 命令;
- 把文本送给外部命令处理,再把结果带回 Vim。
先理解:Ctrl-Z 不是退出 Vim
在 Linux、macOS、WSL 等支持作业控制的 Shell 中,Vim 通常是 Shell 的一个前台作业:
Shell 启动 Vim |
在 Vim 的普通模式中按 Ctrl-Z,通常相当于执行:
:suspend |
有些资料也写成:
:stop |
此时 Vim 进程仍然存在,但它处于 Stopped(暂停) 状态。缓冲区、光标位置、分屏和撤销历史都还在内存中。
回到 Shell 后,最常用的是:
jobs # 查看当前 Shell 管理的作业 |
一个完整练习如下:
vim demo.txt |
在 Vim 中做一点编辑,先执行 :w,然后在普通模式按 Ctrl-Z。回到 Shell 后执行:
jobs -l |
Vim 会回到刚才的位置。
为什么通常不要对 Vim 使用 bg
bg %1 的含义是让已暂停的作业在后台继续运行。然而交互式 Vim 需要读取终端输入;后台进程尝试读取终端时,通常会再次被 Shell 暂停。因此对 Vim 来说,最自然的组合是:
Ctrl-Z → 在 Shell 中做事 → fg |
而不是:
Ctrl-Z → bg |
另外,暂停不等于持久化。关闭终端、Shell 退出或系统异常时,暂停的 Vim 仍可能丢失,所以养成挂起前 :w 的习惯更稳妥。需要跨终端长期保留会话时,应考虑 tmux、screen 或 Vim 的会话文件,而不是依赖 Ctrl-Z。
在 Vim 中执行一条 Shell 命令::!
如果只想执行一条命令,没有必要挂起 Vim:
:!pwd |
命令结束后按回车即可回到 Vim。适合查看目录、Git 状态、脚本输出等短任务。
% 在 Vim 命令行中可代表当前文件名,例如:
:!python3 % |
如果完整路径可能包含空格,更稳妥的写法是:
:execute '!python3 ' . shellescape(expand('%:p')) |
其中 expand('%:p') 得到当前文件的绝对路径,shellescape() 负责按当前 Shell 的规则转义。
临时进入一个子 Shell::shell
如果要连续执行多条命令,可以在 Vim 中输入:
:shell |
简写为:
:sh |
这会从 Vim 启动一个新的子 Shell。在子 Shell 中完成操作后,输入:
exit |
即可回到 Vim。
需要区分两种方式:
Ctrl-Z:暂停 Vim,回到启动 Vim 的原 Shell;:shell:Vim 保持运行,并启动一个新的子 Shell。
如果只是回去执行一两条已有历史命令,我更偏向 Ctrl-Z;如果希望在 Vim 内临时开一段独立的 Shell 工作流,则使用 :shell。
让 Shell 输出进入当前文件::read !
外部命令的输出可以直接插入光标所在行的下一行:
:read !date |
简写为:
:r !date |
这适合插入时间、目录列表、生成的配置片段或脚本结果。若要把输出插到文件第一行之前,可以使用:
:0read !date |
把 Vim 文本交给 Shell::write !
下面的命令不会把文件保存为名叫 wc 的文件,而是把当前缓冲区内容作为标准输入交给 wc -l:
:write !wc -l |
也可以先在可视模式选中几行,再执行:
:'<,'>write !wc -l |
这类操作适合统计、检查或把一段文本送给外部脚本,但不修改当前缓冲区。
使用外部命令直接过滤文本::! 与范围
当 ! 前面带有行范围时,Vim 会把这些行交给外部命令,并用命令输出替换原文本。
对整个文件排序:
:%!sort |
排序并去重:
:%!sort -u |
格式化 JSON:
:%!python3 -m json.tool |
也可以在可视模式选中一部分文本后按 !,输入 sort 并回车,只处理选中区域。
这种操作会修改缓冲区。如果结果不符合预期,立即按 u 撤销即可。第一次练习时最好使用临时文件,并先保存一次。
编译与搜索:让结果进入 quickfix
直接运行 :!make 可以看到输出,但错误位置只是普通文本。使用:
:make |
Vim 会按照 makeprg 和 errorformat 解析编译信息,并把错误放入 quickfix 列表。随后可以使用:
:cnext |
在错误之间跳转。
搜索也可以采用类似工作流:
:grep TODO -R . |
这比在 Vim 外运行命令、手工记住文件名和行号更适合代码阅读。
内置终端::terminal
支持 +terminal 功能的 Vim 可以直接打开终端缓冲区:
:terminal |
也可以在分屏中打开:
:belowright split | terminal |
终端正在接收输入时,可以按 Ctrl-W N 进入 Terminal-Normal 模式,再用 Vim 的窗口命令移动或复制内容;按 i 可重新把输入交给终端程序。
内置终端适合需要持续观察的命令、REPL 或测试进程。只执行一次的短命令仍优先用 :!,这样上下文更简单。
应该选择哪一种方式
| 目的 | 推荐方式 |
|---|---|
| 临时回到启动 Vim 的 Shell | Ctrl-Z,完成后 fg |
| 执行一条短命令 | :!command |
| 连续执行多条 Shell 命令 | :shell,完成后 exit |
| 把命令输出插入文件 | :read !command |
| 把文本送给命令但不改原文 | :write !command |
| 用外部命令替换或格式化文本 | :%!command 或可视选择后 !command |
| 编译并在错误之间跳转 | :make、:copen |
| 在 Vim 内运行持续交互程序 | :terminal |
| 关闭终端后仍要保留工作现场 | tmux 或 screen |
Windows、PowerShell 与 WSL 的差异
Ctrl-Z、jobs、fg 和 bg 这一套来自 Unix Shell 的作业控制。在 Linux、macOS、WSL 中的 Bash/Zsh 里通常可以直接使用;在原生 PowerShell 或 cmd.exe 中,不要假设它有完全相同的行为。
如果当前在 Windows 原生终端中使用 Vim,优先练习这些跨环境方式:
:!command |
若希望完整练习 Ctrl-Z → jobs → fg,建议在 WSL 的 Bash/Zsh 终端中打开 Vim。
20 分钟针对性练习
不要一次背完所有命令,可以按下面顺序练习:
第一轮:只练挂起与恢复(5 分钟)
- 用 Vim 打开一个临时文件;
- 写入两行文字并
:w; - 按
Ctrl-Z; - 执行
jobs -l; - 执行
fg回到 Vim; - 重复三次,直到形成肌肉记忆。
第二轮:在 Vim 内执行命令(5 分钟)
依次执行:
:!pwd |
观察“只显示输出”和“把输出插入文件”的区别。
第三轮:让外部命令处理文本(5 分钟)
准备几行乱序并有重复的单词,执行:
:%!sort -u |
然后按 u 撤销,再重复一次。
第四轮:建立自己的选择习惯(5 分钟)
遇到任务时先问:
- 我只是执行一条命令吗?用
:!; - 我要回到原 Shell 吗?用
Ctrl-Z,再用fg; - 我要把输出放进文章吗?用
:read !; - 我要转换一段文本吗?用范围加
!; - 这是持续运行的交互任务吗?用
:terminal或tmux。
最小速查表
Ctrl-Z 暂停 Vim,回到原 Shell(Unix/WSL) |
常见误区
- 把暂停当成后台运行:
Ctrl-Z后 Vim 是停止状态,通常用fg恢复。 - 用
bg恢复交互式 Vim:它仍需要终端输入,常会再次停止。 - 认为暂停后可以安全关闭终端:暂停不提供持久会话保障。
- 忘记保存再运行程序:外部编译器读取的是磁盘文件,不一定能看到未保存的缓冲区内容。
- 混淆
:!cmd和:%!cmd:前者只执行,后者会用命令输出替换整个缓冲区。 - 忽略路径中的空格:脚本化命令应配合
shellescape()和expand('%:p')。
Vim 内置帮助
遇到版本差异时,以当前 Vim 自带帮助为准:
:help suspend |
这部分学习完成后,下一步最值得继续的是 quickfix 工作流:把编译、测试和全文搜索的结果统一成一个可以跳转的错误列表。