Vim 与 Shell 协作:挂起、恢复与外部命令

为什么要学这一部分

平时把 Vim 当作编辑器使用时,很容易形成这样的习惯:退出 Vim,执行一条 Shell 命令,再重新打开文件。实际上,Vim 和 Shell 之间有多种协作方式。学会后,可以在不丢失当前缓冲区、光标位置、窗口布局和撤销历史的情况下,临时回到 Shell,或者直接让外部命令处理当前文本。

本文是 Shell 协作专题。模式、文本对象、寄存器、宏、批处理和 quickfix 的完整学习主线,见 Vim 实战:从编辑语言到工程导航。

这一部分最值得先掌握的不是大量命令,而是三个层次:

  1. 暂时离开 Vim,再回到原来的编辑现场;
  2. 在 Vim 内执行一条或一组 Shell 命令;
  3. 把文本送给外部命令处理,再把结果带回 Vim。

先理解:Ctrl-Z 不是退出 Vim

在 Linux、macOS、WSL 等支持作业控制的 Shell 中,Vim 通常是 Shell 的一个前台作业:

Shell 启动 Vim
↓
Vim 在前台运行
↓ Ctrl-Z
Vim 被暂停,Shell 回到前台
↓ fg
原来的 Vim 恢复运行

在 Vim 中按 Ctrl-Z 暂停,使用 jobs 查看任务,再用 fg 回到原缓冲区

动态图:Ctrl-Z 暂停的是进程,不是文件;fg 恢复同一个 Vim,因此未保存内容、光标位置和撤销历史仍然存在。

在 Vim 的普通模式中按 Ctrl-Z,通常相当于执行:

:suspend

有些资料也写成:

:stop

此时 Vim 进程仍然存在,但它处于 Stopped(暂停) 状态。缓冲区、光标位置、分屏和撤销历史都还在内存中。

回到 Shell 后,最常用的是:

jobs          # 查看当前 Shell 管理的作业
fg # 恢复最近的作业到前台
fg %1 # 恢复编号为 1 的作业

一个完整练习如下:

vim demo.txt

在 Vim 中做一点编辑,先执行 :w,然后在普通模式按 Ctrl-Z。回到 Shell 后执行:

jobs -l
fg

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
:!ls
:!git status
:!python3 current_script.py

命令结束后按回车即可回到 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
:read !pwd
:read !ls

简写为:

: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
:copen

Vim 会按照 makeprg 和 errorformat 解析编译信息,并把错误放入 quickfix 列表。随后可以使用:

:cnext
:cprevious
:cclose

在错误之间跳转。

搜索也可以采用类似工作流:

:grep TODO -R .
:copen

这比在 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
:shell
:read !command
:terminal

若希望完整练习 Ctrl-Z → jobs → fg,建议在 WSL 的 Bash/Zsh 终端中打开 Vim。

20 分钟针对性练习

不要一次背完所有命令,可以按下面顺序练习:

第一轮:只练挂起与恢复(5 分钟)

  1. 用 Vim 打开一个临时文件;
  2. 写入两行文字并 :w;
  3. 按 Ctrl-Z;
  4. 执行 jobs -l;
  5. 执行 fg 回到 Vim;
  6. 重复三次,直到形成肌肉记忆。

第二轮:在 Vim 内执行命令(5 分钟)

依次执行:

:!pwd
:!ls
:r !date

观察“只显示输出”和“把输出插入文件”的区别。

第三轮:让外部命令处理文本(5 分钟)

准备几行乱序并有重复的单词,执行:

:%!sort -u

然后按 u 撤销,再重复一次。

第四轮:建立自己的选择习惯(5 分钟)

遇到任务时先问:

  • 我只是执行一条命令吗?用 :!;
  • 我要回到原 Shell 吗?用 Ctrl-Z,再用 fg;
  • 我要把输出放进文章吗?用 :read !;
  • 我要转换一段文本吗?用范围加 !;
  • 这是持续运行的交互任务吗?用 :terminal 或 tmux。

最小速查表

Ctrl-Z          暂停 Vim,回到原 Shell(Unix/WSL)
jobs 查看 Shell 作业
fg 恢复最近的前台作业

:!pwd 执行一条外部命令
:sh 启动子 Shell,exit 返回 Vim
:r !date 插入外部命令输出
:w !wc -l 把缓冲区送给外部命令
:%!sort -u 用外部命令过滤整个缓冲区
:make 编译并填充 quickfix
:copen 打开 quickfix 列表
:terminal 打开内置终端

常见误区

  1. 把暂停当成后台运行:Ctrl-Z 后 Vim 是停止状态,通常用 fg 恢复。
  2. 用 bg 恢复交互式 Vim:它仍需要终端输入,常会再次停止。
  3. 认为暂停后可以安全关闭终端:暂停不提供持久会话保障。
  4. 忘记保存再运行程序:外部编译器读取的是磁盘文件,不一定能看到未保存的缓冲区内容。
  5. 混淆 :!cmd 和 :%!cmd:前者只执行,后者会用命令输出替换整个缓冲区。
  6. 忽略路径中的空格:脚本化命令应配合 shellescape() 和 expand('%:p')。

Vim 内置帮助

遇到版本差异时,以当前 Vim 自带帮助为准:

:help suspend
:help :!
:help :shell
:help :read!
:help :write_c
:help filter
:help :make
:help quickfix
:help terminal

这部分学习完成后,下一步最值得继续的是 quickfix 工作流:把编译、测试和全文搜索的结果统一成一个可以跳转的错误列表。

知识共享许可协议