跳过正文
  1. Posts/

Vim 学习笔记 Day 007:命令行模式与常用 Ex 操作,把“会切换上下文”推进到“会对编辑器下指令”

·6485 字·13 分钟
📖 阅读 --
DogDu
作者
DogDu
工作结束或者累了, 要不休息一会, 看会动漫吧 ~
目录
Vim 14 天 - 这篇文章属于一个选集。
§ 7: 本文

今日主题
#

  • 主主题:命令行模式与常用 Ex 操作
  • 副主题:把前几天学过的搜索、替换、buffer、window 接到统一的命令行心智模型上

学习目标
#

  • 先分清“普通模式是在文本里操作”,“命令行模式是在对编辑器下命令”。
  • 掌握最常用的一层 Ex 工作流:
    • 保存、退出、重新打开文件
    • 给命令指定作用范围
    • 把多个命令串起来执行
    • 在命令历史和命令行窗口里回看与修改
  • 建立一个简单判断:什么时候该继续用 dc、文本对象,什么时候该上 :

前置回顾
#

  • Day 003 学的是 operator + motion,是在“当前光标附近怎么改”。
  • Day 004 学的是文本对象,开始按结构改内容。
  • Day 005 学的是搜索、替换、Visual,开始对“一个区域”做修改。
  • Day 006 学的是 buffer / window / split,开始处理多个上下文。
  • Day 007 往前再推一步:不只是“在文本里动手”,而是“告诉编辑器对哪些行、哪些文件、哪些窗口做什么”。

典型场景
#

  • 你改完文件,想保存但不离开当前编辑。
  • 你想退出当前文件,但不想保存本次误操作。
  • 你想把第 10 行到第 20 行统一缩进或替换。
  • 你刚做过一次 :%s/old/new/gc,想把命令拿回来稍微改一下再执行。
  • 你想先列出 buffer,再跳到某个 buffer,或者在一个命令里连续做两步整理。

如果说普通模式更像“直接上手”,那么命令行模式更像“给编辑器发指令”。

最小命令集
#

今天只保留高频、真正能马上用上的一层。

进入命令行模式
#

  • :
    • 进入 Ex 命令行
  • /?
    • 搜索本身也属于命令行模式
  • q:
    • 打开命令行窗口,回看和编辑之前输入过的 Ex 命令
  • q/q?
    • 打开搜索命令行窗口,回看和编辑之前的搜索
  • 命令行中 Ctrl-F
    • 从当前命令行直接进入命令行窗口

最常用文件命令
#

  • :w
    • 保存当前文件
  • :q
    • 退出当前窗口
  • :wq
    • 保存并退出
  • :q!
    • 强制退出,不保存本次修改
  • :e file
    • 打开文件
  • :ls
    • 列出 buffer

范围
#

  • :.
    • 当前行
  • :$
    • 最后一行
  • :%
    • 整个文件
  • :1,5
    • 第 1 到第 5 行
  • :.,$
    • 从当前行到文件末尾
  • :/pattern/,$
    • 从匹配某个模式的行到文件末尾
  • Visual 选区下的 :'<,'>
    • 上一次或当前选区范围

组合与批量
#

  • :%s/old/new/gc
    • 全文件替换并逐个确认
  • :1,5s/foo/bar/g
    • 只在某个范围替换
  • :g/pattern/p
    • 对匹配行执行命令,今天先把它当成“按条件筛行并执行”
  • :ls | b 3
    • | 串联多个 Ex 命令

命令行编辑与历史
#

  • Ctrl-B / Ctrl-E
    • 在命令行里跳到开头 / 结尾
  • Ctrl-W / Ctrl-U
    • 删除前一个词 / 删除到行首
  • Ctrl-R Ctrl-W
    • 把光标下单词直接插进命令行
  • :history
    • 查看命令历史;也可用 :his ::his /
  • @:
    • 重复上一条命令行命令

与外部命令协作
#

  • :!cmd
    • 临时执行外部命令,先看结果,不改当前缓冲区
  • :r !cmd
    • 把外部命令输出读入当前缓冲区
  • :{range}!cmd
    • 把一个范围的文本交给外部命令过滤,并用结果替换原文本
  • :w !cmd
    • 把当前缓冲区内容写到外部命令的标准输入
  • :grep pattern
    • grepprg 搜索并把结果送进 quickfix,比单纯 :!grep 更像 Vim 工作流

它是怎么用的
#

命令行模式不只是冒号
#

本地 cmdline.txt 里把 command-line mode 归到一类:

  • : Ex 命令
  • /? 搜索
  • ! 过滤或调用外部命令

也就是说,Day 005 学过的搜索,其实已经在用命令行模式了。今天只是把这件事说清楚,并把 : 这一支补齐。

Ex 命令的基本句式是“范围 + 命令”
#

最容易抓住的心智模型是:

VIM 代码 · 共 1 行
:[range]command

比如:

VIM 代码 · 共 4 行
:w
:%s/old/new/gc
:1,5d
:.,$>

这几条命令背后都是同一件事:

  • 先决定作用到哪里
  • 再决定做什么

这和 Day 003 的 operator + motion 很像,只不过:

  • 普通模式里常见的是“操作符 + 运动”
  • Ex 里常见的是“范围 + 命令”

范围是 Ex 最该先练顺的部分
#

本地帮助里明确给出了高频地址:

  • {number}
  • .
  • $
  • %
  • /pattern/
  • ?pattern?

以及偏移:

  • .+3
  • /that/+1

实战里先会这几个就很够用:

VIM 代码 · 共 4 行
:1,5p
:.,$d
:%s/foo/bar/g
:/TODO/,$s/foo/bar/gc

真实场景理解:

  • :1,5p 看前 5 行
  • :.,$d 从当前行删到文件末尾
  • :%s/foo/bar/g 全文件替换
  • :/TODO/,$s/foo/bar/gc 从某个 TODO 开始到结尾逐个确认替换

| 让命令行从“单步”变成“一个短流程”
#

本地 cmdline.txt 说明 | 可以分隔多个命令。

例如:

VIM 代码 · 共 1 行
:ls | b 3

它的价值不在于炫技,而在于把一个很短的编辑流程串起来:

  1. 先看 buffer 列表
  2. 再跳到目标 buffer

你也可以这样理解:

  • 普通模式是一步一步手上做
  • Ex 更适合把短流程压成一句话

命令行窗口是“把临时命令变成可编辑历史”
#

q: 很值得早一点记住。

场景是:

  • 你刚打了一条很长的替换命令
  • 发现只想改一个范围或一个 flag
  • 不想从头重打

这时可以:

  1. 输入 q:
  2. 在命令行窗口里找到之前的命令
  3. 像编辑普通文本一样改它
  4. 回车执行

命令行里按 Ctrl-F 也能进去。这个动作能明显降低“复杂命令输错就全部重来”的压力。

命令行本身也有一套很实用的编辑键
#

本地 cmdline.txt 里,命令行并不是“输错了就整条删掉重来”的一次性输入框。

最常用的就是这几组:

  • Ctrl-B
    • 跳到命令行开头
  • Ctrl-E
    • 跳到命令行结尾
  • Ctrl-W
    • 删掉光标前一个词
  • Ctrl-U
    • 删到行首

这在长替换命令里很值钱,因为你经常只是:

  • 范围写错了
  • 一个 flag 想补上
  • 某个单词想改掉

而不是整条命令都得重输。

Ctrl-R Ctrl-W:把光标下单词直接塞进命令行
#

本地 cmdline.txt 还给了一个很实用的命令行补词动作:

  • Ctrl-R Ctrl-W
    • 把光标下的 word 插进当前命令行

这对下面这些场景特别省事:

  • 光标已经停在某个变量名上,准备 :%s/old/new/g
  • 光标已经停在某个配置键上,准备 /:grep

例如你在普通模式光标停在 timeout 上,然后输入:

VIM 代码 · 共 1 行
:%s/

接着按:

VIM 代码 · 共 1 行
Ctrl-R Ctrl-W

就能把 timeout 直接带进命令行,不用重新手打一遍。

:historyq/@::Ex 和搜索都可以回看和重跑
#

Learn-Vim 的命令行章节和本地帮助都强调了一个点:

  • 命令行历史不是只有 q:

你至少可以先会这三组:

  • :history
    • 看 Ex 历史
  • q/q?
    • 看搜索历史并直接改
  • @:
    • 重复上一条命令行命令

所以如果你刚刚跑过:

VIM 代码 · 共 1 行
:%s/foo/bar/gc

接下来想原样再跑一次,或者只改一点点,就不必从头重打。

:!:r !:{range}!:w ! 是 Vim 和命令行工具最自然的接口
#

本地 cmdline.txt 直接把这些形态并列列出来了:

  • :!cmd
  • :r !cmd
  • :w !cmd

同一个帮助文件里还给了非常典型的例子:

VIM 代码 · 共 3 行
:!ls | wc
:r !ls | wc
:r !date

本地 change.txt 还单独说明了 filter 命令:

  • !{motion}{filter}
  • :{range}!{filter}

也就是说,命令行模式和外部命令协作,大致可以分成四种心智模型:

  1. 只看外部命令输出,不改缓冲区:
VIM 代码 · 共 3 行
:!grep -n TODO %
:!head -n 20 %
:!tail -n 30 /var/log/app.log
  1. 把外部命令输出插入到文本里:
VIM 代码 · 共 3 行
:r !date
:r !head -n 5 README.md
:r !grep -n ERROR /var/log/app.log | tail -n 10
  1. 把一段文本交给命令处理后替换原文:
VIM 代码 · 共 3 行
:%!awk 'NF'
:%!sort | uniq
:'<,'>!awk '{print toupper($0)}'
  1. 把当前缓冲区当作标准输入送给命令:
VIM 代码 · 共 3 行
:w !wc -l
:'<,'>w !cat
:%w !awk 'END { print NR }'

这里最容易混的地方是:

  • :!cmd 是“我去看命令输出”
  • :r !cmd 是“我把命令输出拿回来放进文本”
  • :{range}!cmd 是“我把这段文本交给命令改完再放回来”
  • :w !cmd 是“我把当前文本喂给命令”

如果你一开始先把这四种动作分清,命令行模式就会一下子具体很多。

常见操作套路
#

套路 1:改完就保存,但继续留在当前上下文
#

场景:

  • 你在一个文件里已经改完这一轮
  • 不想退出,只是先落盘

命令:

VIM 代码 · 共 1 行
:w

这是最基础但最高频的 Ex 命令。

套路 2:只对当前到文件末尾做处理
#

场景:

  • 你光标已经移动到一个段落中间
  • 想从这里往后统一处理

命令:

VIM 代码 · 共 1 行
:.,$s/foo/bar/gc

这个句式很有代表性,因为它把:

  • 当前定位能力
  • 搜索替换
  • Ex 范围

三件事连起来了。

套路 3:先选中,再交给 Ex
#

场景:

  • 你肉眼很容易框出一段代码
  • 但对这段内容想做的是 Ex 命令,比如替换、缩进、格式化

做法:

  1. 先用 VCtrl-V 选区
  2. :
  3. Vim 会自动补出 :'<,'>

例如:

VIM 代码 · 共 2 行
:'<,'>s/foo/bar/g
:'<,'>>

这套很适合把 Visual 和命令行模式接起来。

套路 4:看完历史命令后再微调重跑
#

场景:

  • 你刚跑过一次很长的 :s
  • 想把范围从 % 改成 .,$
  • 或者把 g 改成 gc

做法:

  1. q:
  2. 找到上一条命令
  3. 修改后执行

这比重新打一遍更稳。

套路 5:buffer 工作流和 Ex 串联
#

场景:

  • 你想看 buffer 列表后立刻跳转

命令:

VIM 代码 · 共 1 行
:ls | buffer 2

Day 006 学的是“怎么切”,Day 007 学的是“怎么把切换也纳入一条命令”。

套路 6:要“看外部世界”,先用 :!cmd
#

场景:

  • 你不想离开 Vim
  • 只是想临时看一下 shell 命令结果

命令:

VIM 代码 · 共 3 行
:!grep -n TODO %
:!head -n 20 %
:!tail -n 50 /var/log/nginx/access.log

这类命令的重点是:

  • 先看
  • 不改缓冲区
  • 看完回到当前编辑

如果你只是临时查一下文件内容、日志尾部、某个关键字,这通常是最轻的一种协作方式。

套路 7:要“把结果带回文本”,用 :r !cmd
#

场景:

  • 你要在文档里插入命令结果
  • 或者要摘一小段外部输出做记录

命令:

VIM 代码 · 共 3 行
:r !date
:r !head -n 5 ~/.bashrc
:r !grep -n ERROR app.log | tail -n 10

真实场景包括:

  • 写笔记时插入当前日期
  • 插入某个文件头部几行做引用
  • 把日志里最近几条匹配结果贴进排查记录

这个动作很适合把命令行输出变成“编辑素材”。

套路 8:要“改文本本身”,用 filter 命令
#

场景:

  • 你已经在缓冲区里有一段文本
  • 现在想借助 Unix 工具快速改形状

命令:

VIM 代码 · 共 3 行
:%!awk 'NF'
:%!sort | uniq
:'<,'>!awk '{print NR ": " $0}'

理解方式是:

  • :%!awk 'NF'
    • 删除空行,只保留有内容的行
  • :%!sort | uniq
    • 整个文件排序后去重
  • :'<,'>!awk '{print NR ": " $0}'
    • 只改选中的那段文本

这个模式和 Day 003 / Day 007 很接近:

  • Day 003 是“操作 + 范围”
  • Day 007 是“范围 + 命令”
  • filter 是“范围 + 外部命令”

套路 9:要“把当前文本喂给命令”,用 :w !cmd
#

场景:

  • 你想把缓冲区内容交给某个命令处理
  • 但不一定要把结果写回缓冲区

命令:

VIM 代码 · 共 3 行
:w !wc -l
:'<,'>w !cat
:%w !awk 'END { print NR }'

这类动作适合:

  • 快速统计行数
  • 验证某个范围到底会传给命令什么内容
  • 把当前文件内容送给某个接收标准输入的工具

其中 :'<,'>w !cat 虽然看起来简单,但很适合练习理解:

  • 你写出的到底是整个文件,还是某个选区
  • :w !cmd 到底是“保存文件”还是“发给命令”

套路 10:grep 优先分清 :!grep:grep
#

场景:

  • 你只是想看 grep 输出
  • 或者你想进入 Vim 的 quickfix 工作流

对比:

VIM 代码 · 共 2 行
:!grep -n TODO %
:grep TODO %

区别是:

  • :!grep
    • 更像临时 shell 调用,只是看输出
  • :grep
    • 会走 Vim 的 grepprg,并把结果送进 quickfix

所以:

  • 临时看结果,用 :!grep
  • 想继续在结果里跳转、逐个处理,用 :grep

环境差异:vim / nvim / LazyVim
#

Vim 和 Neovim 这一层基本一致
#

今天这组能力主要属于 Vim/Neovim 的共同底层:

  • :w :q :e
  • 范围
  • :s
  • | 串联
  • q: 命令行窗口

本地确认环境是:

  • Vim 9.2
  • Neovim 0.12.0

这些基础语义在两边是一致的。

补充一点:

  • :!:r !:{range}!:w ! 这些接口在 vimnvim 里都成立
  • 真正变化更大的通常不是 Vim 这一侧,而是外部 shell 本身

当前本机是 Windows / PowerShell,所以这一节新增的 grepawkcatheadtail 例子,主要按 Unix-like shell 场景讲解;Vim 这一层接口语义来自本地帮助文档,命令写法则以 Unix 常见工作流为主。

Neovim 进入后,命令行模式不会消失
#

到了 Neovim,你可能更常:

  • 用 Telescope 找文件
  • 用 LSP 跳定义
  • 用终端模式跑命令

但真正进入缓冲区后,很多收尾动作还是会落回:

  • :w
  • :q
  • :%s
  • :'<,'>

所以 Day 007 学的不是“过时命令”,而是后面所有工作流的稳定落点。

LazyVim 会把很多入口做得更快,但不会替代 Ex 的表达力
#

LazyVim 以后会给你:

  • 文件搜索入口
  • buffer 切换入口
  • 项目内搜索入口

但当你已经进入编辑细节,需要表达“从这里到那里,对这批行做这件事”时,Ex 仍然是最短语言。

今日练习(5-10 分钟)
#

练习材料
#

新建一个临时文件,内容如下:

TXT 代码 · 共 8 行
alpha one
alpha two
beta one
beta two
todo start
alpha three
beta three
alpha four

练习任务
#

  1. :w 保存一次。
  2. :1,4p 查看前四行。
  3. 把第 1 到第 4 行里的 alpha 改成 item
VIM 代码 · 共 1 行
:1,4s/alpha/item/g
  1. 把从 todo start 所在行到文件末尾的 beta 改成 task,并逐个确认:
VIM 代码 · 共 1 行
:/todo start/,$s/beta/task/gc
  1. 用 Visual 行模式选两三行,按 :,观察自动出现的 :'<,'>
  2. 对选中的行执行一次替换或缩进。
  3. 输入一条稍长的替换命令,再用 q: 打开命令行窗口,把它改短后重新执行。
  4. 最后试一次:
VIM 代码 · 共 1 行
:ls | b 1
  1. 光标停在某个已有单词上,输入 :%s/,再用 Ctrl-R Ctrl-W 把它插进命令行。
  2. 试一次 :history,再试一次 @:.

补充练习:命令行工具协作(Unix 场景)
#

如果你平时会在 Unix / Linux / macOS shell 里工作,再补一轮这组:

  1. 只看,不改缓冲区:
VIM 代码 · 共 2 行
:!head -n 5 %
:!grep -n alpha %
  1. 把结果插进文本:
VIM 代码 · 共 2 行
:r !date
:r !tail -n 3 %
  1. 把整个文件交给外部命令过滤:
VIM 代码 · 共 2 行
:%!awk 'NF'
:%!sort | uniq
  1. 选中几行后,只过滤选区:
VIM 代码 · 共 1 行
:'<,'>!awk '{print "[" NR "] " $0}'
  1. 把当前文本送进命令,而不是替换文本:
VIM 代码 · 共 1 行
:w !wc -l

完成标准
#

  • 能说出 Ex 的基本句式是“范围 + 命令”。
  • 能分清 %.,$'<,'> 各自代表什么范围。
  • 能把 Visual 选区交给 : 继续处理。
  • 能在复杂命令输错后想到 q:,而不是从头重打。
  • 能在命令行里自然使用 Ctrl-BCtrl-ECtrl-WCtrl-U
  • 能在合适的时候想到 Ctrl-R Ctrl-W@:.
  • 能分清 :!cmd:r !cmd:{range}!cmd:w !cmd 的区别。

今日问题与讨论
#

我的问题
#

  • 暂无。本节先把 Day 007 的最小心智模型搭起来,后续问答直接补在这里。

外部高价值问题
#

问题 1:Ex 范围和普通模式的 motion,本质上是不是一回事?
#

  • 问题:
    • Day 003 学的是 d2w 这种“操作 + 范围”,Day 007 又变成了 :1,5d。这两者是不是同一种思路?
  • 简答:
    • 是同一类思路,但表达层不同。普通模式更偏局部、即时;Ex 更偏明确指定一段行范围后批量执行。
  • 场景:
    • 局部删几个词,用 dw 更快。
    • 已经知道是第 20 到第 40 行都要处理,用 Ex 更直接。
  • 依据:
    • 本地 cmdline.txt 的 Ex range 说明。
    • Day 003 的 operator + motion 心智模型。
  • 当前结论:
    • 可以把 Ex 理解成“面向行范围和批量动作的另一种编辑句式”。
  • 是否需要后续回看:
    • 需要。等进到 :global:vglobal:normal 时会更清楚。

问题 2:为什么 Visual 选区后按 :,会自动带上 '<,'>
#

  • 问题:
    • 这个范围到底表示什么?
  • 简答:
    • 它表示当前或上一次 Visual 选区的起点和终点。
  • 场景:
    • 你已经框出一段文本,接下来不想手工数行号,而是直接对选区执行替换、缩进、打印。
  • 依据:
    • 本地 Ex range 机制和 Visual 选区行为。
  • 当前结论:
    • :'<,'> 是把 Visual 选区无缝交给 Ex 的桥梁。
  • 是否需要后续回看:
    • 需要。后面进入代码格式化或批量注释时会频繁用到。

问题 3:Vim 里怎么和 Unix 命令行工具配合?
#

  • 问题:
    • 比如 grepawkcatheadtail 这些,应该在 Vim 里怎么接?
  • 简答:
    • 先按四类动作分:
      • :!cmd 看输出
      • :r !cmd 读输出进缓冲区
      • :{range}!cmd 过滤文本并替换
      • :w !cmd 把文本送给命令
  • 场景:
    • :!tail -n 20 app.log
      • 临时看日志尾部
    • :r !date
      • 把日期插进笔记
    • :%!awk 'NF'
      • 清理空行
    • :w !wc -l
      • 把当前文件内容送给命令统计
  • 依据:
    • 本地 cmdline.txt:!cmd:r !cmd:w !cmd 以及 | 的示例
    • 本地 change.txt 对 filter 命令的说明
  • 当前结论:
    • 对命令行工具的协作,本质上还是 Day 007 的同一套句式,只是把“命令”换成了“外部命令”。
  • 是否需要后续回看:
    • 需要。后面如果进入 :make、quickfix、Neovim 终端,再回看会更顺。

问题 4:为什么 q: 不够,还要记 q/@:
#

  • 问题:
    • 我已经会 q: 了,为什么还要再补搜索历史和“重复上一条命令”?
  • 简答:
    • 因为命令行模式不只承载 :,还承载 /? 和外部命令;它们也都值得回看与重跑。
  • 场景:
    • 你刚做过一次搜索,想把模式稍微改一下;或者刚执行过一条 Ex 命令,想原样再跑一次。
  • 依据:
    • 本地 cmdline.txtq:q/q? 的说明。
    • 本地 repeat.txtchange.txt@: 的说明。
  • 当前结论:
    • q: 管 Ex 历史,q/ 管搜索历史,@: 管重复上一条命令行命令。
  • 是否需要后续回看:
    • 需要。等后面进入更复杂的替换、全局命令和 quickfix,会更常用。

常见误区或易混点
#

  • 误区 1:把命令行模式理解成“只会保存退出”。
    • 实际上搜索、范围替换、批处理都在这里。
  • 误区 2:一看到 : 就想背很多命令。
    • 今天先抓最常用的几条,不需要背 Ex 百科全书。
  • 误区 3::%s 会了,就以为范围不重要。
    • 真正常用的是“不是整文件,而是某个范围”。
  • 误区 4:复杂命令输错后从头重打。
    • 先想到 q: 或命令行里的历史回看。
  • 误区 5:把 :w! file:w !cmd 混掉。
    • 前者是强制写文件,后者是把内容写给外部命令,中间那个空格很关键。
  • 误区 6:看到外部命令就默认用 :!cmd
    • 如果你是想把结果带回文本,应该优先想到 :r !cmd;如果是想改文本本身,应该想到 :{range}!cmd

扩展内容
#

  • :r !cmd
    • 把外部命令输出读入当前缓冲区,例如插入一个命令结果。
  • :!cmd
    • 临时执行外部命令。
  • :{range}!cmd
    • 把指定范围通过外部命令过滤,例如 :%!sort
  • :grep
    • :!grep 更接近 Vim 工作流,因为结果会进入 quickfix。
  • :g/pattern/cmd
    • 对匹配行批量执行命令,后面可以专门展开。
  • Ctrl-R aCtrl-R Ctrl-LCtrl-R Ctrl-F
    • 命令行里还能插寄存器、当前行、当前文件名,后面可作为提速点。

今日小结
#

  • 今天真正要建立的不是“背多少条冒号命令”,而是一个新句式:
    • :[range]command
  • 普通模式解决的是“眼前这一下怎么改”。
  • Ex 解决的是“对哪一批内容做什么”。
  • 一旦把这个心智模型立住,前面的搜索、替换、Visual、buffer 管理都会开始变得更统一。

明日衔接
#

  • 下一天先不急着进入 Neovim
  • 会先进入 Vim 高频进阶整合,把前 7 天里已经碰到、但还没系统收进去的高频能力补齐:
    • 插入模式里的高频入口
    • .、撤销、寄存器
    • 宏的最小工作流
    • :global
    • 多文件批处理
  • 这样再进入 Neovim 时,会更像是在换宿主环境,而不是一边补 Vim 基础一边学新环境。

复习题
#

  1. 命令行模式只包含 : 吗?/? 算不算?
  2. Ex 最基础的句式是什么?
  3. %.,$'<,'> 分别代表什么范围?
  4. 什么时候 dw 更合适,什么时候 :1,5d 更合适?
  5. 复杂命令打错后,除了重输,还有什么更稳的办法?
Vim 14 天 - 这篇文章属于一个选集。
§ 7: 本文