寄存器与剪贴板:复制为什么会被删除顶掉
学习目标
- 解开 Vim 最著名的「bug」:yy 复制 → dd 删除 → p 粘出来的是被删的行,并练成
"0p肌肉自救 - 建立寄存器 = 一排抽屉的心智模型:无名、数字、命名、只读、黑洞、系统剪贴板,各类的「谁写它、拿来干嘛」一张表认清
- 掌握命名寄存器
"a-"z与大写追加("A)—— 收集散落内容、为第 10 章宏打前站 - 学会黑洞寄存器
"_:删除不污染,「复制保真」的第二条路 - 打通系统剪贴板
"+:vim 与微信/浏览器/IDE 之间自由搬运,并了解两边的自动打通配置 - 会用
:reg查看寄存器内容,丢失前先看清单
1. 一个「bug」引出的一章
每个 Vim 用户都撞过这堵墙:用 yy 复制了一行,顺手 dd 删掉一行不要的,然后 p 想粘贴 —— 粘出来的是刚删掉的那行,复制的东西像被偷走了。第一次遇到的人几乎都以为是 bug,或者以为是自己按错了,重试一遍,还是删掉的行。
这不是 bug,是 Vim 剪贴板模型的必然结果,看懂它就看懂了整章:Vim 的默认粘贴源叫「无名寄存器」,它只有一个坑,而 y(复制)和 d(删除)都往这个坑里写。你复制了 title,又删了 note —— 坑里现在是 note,因为后来的写把先来的顶掉了。Vim 这样设计有自己的道理(删除即「剪切」,随手粘贴刚删的内容本来就是高频操作),但代价就是「复制」不设防。
值得花一分钟体会这个设计的来龙去脉。Vim 的世界里「删掉一段、马上贴到别处」(挪动)是重构里最高频的动作,于是删除顺理成章地承担了「剪切」的职责 —— 默认不设防换来的是挪动零成本。而真正的「复制后待用」,Vim 给了专门保险柜("0)和一排手动抽屉("a-"z):它不是没有解决方案,而是把「默认通道留给最高频的动作」。理解了这层取舍,你就不会再纠结「为什么默认这么坑」,而是记住本章的主旋律:默认通道跑高频,重要内容走保险。
解药在演示的结尾:"0p —— 从 0 号寄存器取。0 号寄存器是 yank 的专属保险柜:每次 y 复制,内容同时写入无名寄存器和 "0;而 d 只写无名寄存器,碰不到 "0。所以「复制完删了点东西,现在要粘贴刚才复制的」的完整答案永远是 "0p。这一章的任务就是把这个坑认识全,顺便打开它的兄弟姐妹 —— 一共 48 个左右。
2. 心智模型:一排抽屉,各有分工
寄存器(register)就是 Vim 内部的多格剪贴板。普通编辑器只有一格(系统剪贴板),复制新内容顶掉旧内容;Vim 有一排抽屉,每格有名字、有分工、有写入规则。总表先认脸,后面逐个开柜:
| 类别 | 名字 | 谁写它 | 拿来干嘛 |
|---|---|---|---|
| 无名寄存器 | "" | y d c s x —— 所有复制删除默认都写它 | 默认粘贴源;一个坑,后来者顶掉先来者 |
| yank 保险柜 | "0 | 只有 y 写它(与 "" 同步各存一份) | "0p 复制保真的标准答案 |
| 数字删除梯 | "1–"9 | 整行/多行删除依次入 "1,旧内容向后顺延 | 后悔药:取回前几刀删掉的内容 |
| 行内小件 | "- | 不足一行的删除(x dw 等) | 小件删除也有存档,很少手动取 |
| 命名抽屉 | "a–"z | 只有你显式写("ayy) | 手动行李箱:存住重要内容随便折腾;大写追加 |
| 只读三件 | ". ": "% | Vim 自动维护 | 最近插入文本 / 最近命令 / 当前文件名 |
| 黑洞 | "_ | 只有你显式写("_d) | 纯删除不留档 —— 保护无名寄存器不被污染 |
| 系统剪贴板 | "+ "* | 你显式写,或与系统共享 | 与 IDE/浏览器/微信 互通 |
读表的正确姿势不是背,是回答三个问题:①谁有权写(默认写?只有 y 写?只有我写?谁都不写?)②会不会被顶掉③什么时候值得指名取。日常 95% 的场景只用三格:""(默认)、"0(保真粘贴)、"+(对外搬运);但「命名抽屉 + 黑洞」这两个主动技能,用会上瘾。
用一个重构下午的场景把表激活:你正在改一个函数 —— yy 复制了签名准备参考,顺手 dd 删了三行废代码("" 被顶了三次,但 "0 里签名还在);又想把函数里某个待办事项挪到周报里,"ayy 存进 a 抽屉;继续删删改改(传送带 "1-"9 记着你的删除史);最后 "0p 贴出签名比对,"+yy 把结论发进聊天工具。整个下午,你其实一直在寄存器之间调度 —— 只是以前不知道,现在知道了,调度就变成了主动的。
3. 无名寄存器 "":默认的公共坑
先把这个最熟的坑说透。d c s x y 全部默认写 ""(两个双引号)—— 这意味着 Vim 里「删除」其实全是「剪切」。这个设计的好处真实存在:改代码时大量操作是「把这段挪走」,删完立刻 p 贴到别处,零额外按键。坏处你也见识了:它对「复制」不设防。所以给一条使用纪律:
纪律:复制了打算待会儿用的东西,中间又必须删内容时 —— 要么粘贴前想一句「粘谁」,要么删除时就地隔离(黑洞,§6)。注意 "" 里按行/字符分开粘贴行为:整行复制粘贴自动落在上/下方(第 7 章 V 的伏笔),字符复制粘贴从光标后接上 —— 这个形状语义第 5 章讲过,寄存器只是仓库,形状跟着内容走。顺带复习 P(大写):粘到光标前/当前行上 —— p 与 P 的选择,看你想让新内容落在锚点的前面还是后面;交换两字符的 xp、交换两行的 ddP,都是这个形状语义的应用(第 5 章的老朋友)。
4. "0 与数字梯 "1-"9:保险柜与传送带
两个「自动保险」系统。0 号是 yank 保险柜(§1 已讲),数字梯是删除史传送带:
规则一句话:每次行级删除,新内容进 "1,原 "1–"8 依次后移一位("9 掉出传送带消失)。所以 "1p 永远是「最近一刀」,"2p 是倒数第二刀 —— 演示里三连删之后用 "2p 取回了中间那刀。不足一行的删除(x、dw)不占传送带,进 "- 小件柜。:reg 随时打开全柜清单 —— 「刚才删的东西还能找回来吗?」这个问题,先 :reg 再回答。
两个「保险柜」的分工容易混,记一组对照就清:"0 是复制的保险柜,"1 是删除的最近档 —— y 永远不进数字梯,d 永远不碰 "0。所以「粘我刚复制的」→ "0p;「粘我上一刀删的」→ "1p(其实直接 p 就是它,"1p 的价值在于连删几刀后取历史)。一个数字冷知识:行级删除只有不指定寄存器时才走传送带,"add 删进 a 抽屉的内容不会占据 "1 —— 指定抽屉的操作完全绕开自动系统,这就是 Vim 寄存器「显式优先于隐式」的统一哲学。
5. 命名寄存器 "a-"z:你的手动行李箱
26 个抽屉 "a–"z,规则极简:只有你显式指名时才读写,其他任何操作不碰它们。语法是「前缀 " + 名字 + 动词」:"ayy 复制进 a、"bdd 删除进 b、"ap 从 a 取。它的价值在于「存住,随便折腾」:
演示里的「收集散落各行」是经典用法:写周报挑重点行、重构时归拢同主题代码。大写字母是追加开关:"Ayy 往 a 抽屉追加而不是覆盖 —— 抽屉变成购物车。这里埋一颗重要的种子:第 10 章的宏就存在命名寄存器里(qa 录制 = 往 a 写按键序列,@a 执行 = 从 a 读),所以「前缀 + 名字」的手感现在练熟,宏那章直接白捡。
抽屉的使用习惯也值得立两条:①一屉一用 —— 给 a 固定安排「当前任务收集」、给 b 安排「常用代码片段」,位置稳定,肌肉记忆才有意义;终端 Vim 里抽屉内容关掉重开也还在(shada/viminfo 负责持久化),但 IDE 插件对寄存器的跨会话保留行为不一 —— 重要内容别指望隔夜,当天用完当天清:塞满杂物的抽屉等于没有抽屉,定期用 "ayy 覆盖重置即可。②追加注意形状 —— 行级追加行、字符追加字符,混着追加会在粘贴时得到奇怪的拼接,收集场景统一用行级(yy/dd)最稳。
6. 黑洞 "_ 与只读三件
黑洞寄存器 "_ 解决「删除污染」的另一头:把删除的内容扔进一个谁也读不到的地方 —— 纯删除,不留档、不顶任何抽屉:
使用场景一句话概括:刚复制了东西,接下来要删的内容都是垃圾 —— "_dd、"_diw 批量清理,清完 p 依然粘出你的宝贝。它和 "0p 是同一问题的两个解法:前者在删除时隔离,后者在粘贴时取保险柜 —— 黑洞更从容(不用记粘贴时换键),"0p 更普适(忘了隔离也能救)。都要会。
只读三件顺路认识:". 存最近一次插入模式敲的文本(想再贴一遍刚打的一段字,插入模式里 Ctrl+r.);": 存最近一条命令;"% 存当前文件名(写 :e %:h 之类命令时有用)。它们只读,写不进去,当系统提供的三个小便利贴即可。
7. 系统剪贴板 "+ 与 "*:对外口岸
Vim 世界与系统剪贴板之间有两个口岸:"+(selection clipboard 的「主口岸」)和 "*。macOS 上两者等价;Linux 图形环境下 "* 是「鼠标选中即复制」的 primary selection,"+ 才是传统剪贴板 —— 跨平台开发时知道这个区别能省很多困惑。用法照旧是前缀语法:
在 IDE 里这条通道尤其宽松:VSCodeVim 和 IdeaVim 都可以配置成默认互通(y/p 直接走系统剪贴板,配置见本章 IDE 备注)。互通有互通的代价:你的 vim 复制会顶掉系统剪贴板里网页复制的内容 —— 两条通道混成了一条。个人建议:默认不互通,需要对外时显式 "+ —— 按键多两下,换「vim 内部流转」与「对外搬运」互不干扰。当然高频对外搬运的人反向选择也合理,这是口味题,不是对错题。
展开说一句「双通道」哲学:不互通的配置下,你其实握着两条独立的剪贴板 —— vim 寄存器集群跑内部流转(改代码、挪内容,快进快出),系统剪贴板跑对外交互(微信、浏览器、issue)。两条通道各干各的,谁也不顶谁:从网页复制一段代码,随手在 vim 里 y 了别的东西,再 "+p —— 网页那段还在。互通配置则把两条通道焊死成一条,少按两键,但「刚才网页复制的被我的 y 顶了」这类事故也从此常驻。想清楚自己哪类操作更频繁,选择自然就有了。
8. :reg 与两个小入口
:reg(registers)打开全部抽屉的清单:每个名字 + 内容摘要,按回车关闭。丢东西了先看它;想确认 "a 里存的什么也看它。两个顺手的补充:①清单里只显示有内容的抽屉,空的不占屏;②:reg a 只看指定的几个(:reg a b 0)。取内容的入口不止 p:插入模式里 Ctrl+r + 名字,把抽屉内容「打」进当前文本(比如 Ctrl+r% 插入文件名)—— 写文档、拼命令时极顺手。
9. 决策表:复制粘贴的完整攻略
把全章收敛成一张「遇到什么,按什么」:
| 场景 | 标准动作 | 原理 |
|---|---|---|
| 复制了、没删过东西、现在粘贴 | p | 无名寄存器还是复制的内容 |
| 复制后删过东西,要粘【刚删的】(挪动) | p | 删除=剪切,本来就是它 |
| 复制后删过东西,要粘【之前复制的】 | "0p | 0 号是 yank 保险柜 |
| 要删垃圾,但待会儿还要粘贴刚复制的 | "_d…(黑洞删) | 删除不顶无名寄存器 |
| 存几样东西,中间大量复制删除 | "ay… 存,"ap 取 | 命名抽屉不被动 |
| 收集散落的多行 | "Ayy 逐行追加 | 大写=追加模式 |
| 找回收删掉的东西 | :reg 查看,"1p/"2p 取 | 数字梯存最近 9 刀 |
| 与浏览器/微信/同事传代码 | "+y… / "+p | 系统剪贴板口岸 |
10. 每日训练计划(30 分钟 × 2 天 + 复习)
| 天 | 时间盒 | 内容 | 通过标准 |
|---|---|---|---|
| 第 1 天 | 0–10 分钟 | 重放演示 1、5,读 §1–§4;靶场复现陷阱 5 遍(yy→dd→p→u→"0p) | "0p 不假思索 |
| 10–30 分钟 | 练习 1 + 练习 3 | 黑洞与 "0p 两条路都走得顺 | |
| 第 2 天 | 0–10 分钟 | 重放演示 2、4,读 §5–§8;"ayy/"Ayy/"ap 三连打 10 组 | 前缀语法不看键盘 |
| 10–30 分钟 | 练习 2 + 练习 4 | 盲测 10 题对 7 题,本章毕业 |
11. 如果本章只记住三件事
- 复制会被删除顶掉,不是 bug:无名寄存器一个坑,y 和 d 都写 —— 粘「之前复制的」用
"0p(yank 保险柜); - 主动技能两个:命名抽屉
"a…存住随便折腾(大写追加,宏的预备课),黑洞"_d…删除不污染; - 对外走
"+,对内走默认:系统剪贴板是口岸不是主场;丢了东西先:reg—— 数字梯里存着最近 9 刀。
内容能存取了,「重复劳动」还差最后一块拼图:第 10 章的撤销、. 重复与宏 —— 而宏,就寄存在你刚练熟的命名抽屉里。
练习
练习 1:陷阱复现与自救(10 分钟)
靶场 5 组,每组:①yy 复制一行 ②j 到下一行 dd 删除 ③p 观察粘出的是删掉的行 ④u 撤销 ⑤"0p 粘出真正复制的行 ⑥两次 u 回到原状。通过线:第③步能提前说出会粘出什么,第⑤步不需要回想按键。
练习 2:行李箱收集(10 分钟)
一段 ≥15 行代码,任务是收集其中 3 行散落的重点行:①"ayy 存第一行;②中间做 3 次无关的 y/d 操作(污染无名寄存器);③"Ayy 追加第二行、再去第三行同样追加;④文件末尾 o 开新行 Esc,"ap 取出三行。验证:取出的顺序与收集顺序一致,中间的污染操作没有影响任何内容。
练习 3:黑洞隔离对照实验(10 分钟)
两遍对照:A 遍:yiw 复制一个词 → 到垃圾行 dd → p,亲眼看粘出垃圾行 → "0p 才是对的。B 遍:同样 yiw → "_dd 黑洞删 → p 直接粘出正确的词。各 5 遍。通过线:两种场景(先污染后救 / 一开始就隔离)都不用想超过一秒。
练习 4:盲测毕业考(10 分钟)
10 题先说答案再看:①yy 后 dd,p 粘出谁的内容?②粘「之前 yank 的」怎么按?③把当前行复制进 a 抽屉 ④把当前行【追加】进 a 抽屉 ⑤从 a 取出粘贴 ⑥删除但不污染无名寄存器 ⑦复制当前行到系统剪贴板 ⑧查看所有寄存器 ⑨最近一次删除存在哪个寄存器?⑩倒数第二刀删除怎么取回?答案:dd 的 / "0p / "ayy / "Ayy / "ap / "_dd(或 "_dw 等) / "+yy / :reg / "1 / "2p。10 对 7 毕业。
常见陷阱
"_ 黑洞化;②粘贴前默想「无名里现在是谁」,复制的内容用 "0p 兜底;③重要内容一开始就 "a 存抽屉。三条殊途同归:别裸奔着穿过「复制-删除-粘贴」的雷区。
"1 永远是最近一刀,你惦记的那刀可能已经在 "2 "3 了。别猜,:reg 打开清单看一眼,指名取。另外注意:不足一行的删除(x dw)不占传送带,别在数字梯里找它们(在 "-)。
"* 是「鼠标划选即复制」的 primary selection,"+ 才是 Ctrl-c/Ctrl-v 的剪贴板 —— 远程开发、容器、WSL 里行为还会再变。经验法则:写教程、写脚本统一用 "+,行为最一致;IDE 里则建议直接用 IDE 的 Cmd-c/Cmd-v(见下条)。
vim.useSystemClipboard / set clipboard+=unnamed)很方便,但代价:①vim 里每次复制都会顶掉系统剪贴板(刚从网页 Cmd-c 的东西没了);②yank-then-delete 陷阱的战场从 vim 内部搬到系统层,一样存在。高频对外选互通,高频内部选隔离 + 显式 "+ —— 两种都试一周再定,别只看教程默认值。
VSCode / IDEA 差异
VSCode(VSCodeVim)
- 寄存器系统 VSCodeVim 完整实现:
"0p、"a…、"_…、:reg全部照本章用 - 系统剪贴板:①显式通道
"+y/"+p可用;②可视模式里 Cmd+c/Cmd+v 直通系统;③设置"vim.useSystemClipboard": true让 y/p 默认互通(取舍见陷阱四) - VSCode 原生的复制粘贴(Cmd+c/v,插入模式或无模式选中时)与 vim 寄存器是两套体系,互不干扰 —— 这其实是天然的「内部/对外」双通道
IntelliJ IDEA(IdeaVim)
- 寄存器同样完整支持;
:reg清单由 IdeaVim 自绘,内容与 IDEA 的剪贴板历史(⌘⇧V 粘贴历史弹窗)分开存 - 系统剪贴板:显式
"+可用;自动互通写.ideavimrc:set clipboard+=unnamed(推荐放第 12 章的配置全文里一起定夺) - IDEA 特有福利:Cmd+Shift+V 打开粘贴历史(最近 N 条系统复制),相当于把系统剪贴板也做成了一排抽屉 —— 与 vim 数字梯互补
本章速记
| 按键 / 命令 | 作用 | 按键 / 命令 | 作用 |
|---|---|---|---|
""(无名) | 默认粘贴源,y/d 都写(一个坑) | "0p | 取 yank 保险柜 —— 复制保真 |
"1–"9 | 删除传送带(最近 9 刀) | "- | 行内小件删除存档 |
"ayy / "ap | 存入 a 抽屉 / 从 a 取 | "A… | 大写 = 追加不覆盖 |
"_d… | 黑洞:纯删除不污染 | "+y… / "+p | 系统剪贴板进出 |
:reg | 查看全部抽屉清单 | ". ": "% | 只读:最近插入/命令/文件名 |
一句话心法:复制会被删除顶掉("0p 保真),要主动就用抽屉("a 存 "A 追)与黑洞("_ 删),对外走 "+,丢了先 :reg。