ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vim多行删除超全指南:寄存器、全局命令与文本对象实战

Vim多行删除超全指南:寄存器、全局命令与文本对象实战 Vim 多行删除这件事说简单也简单dd谁都会按但说复杂也复杂——我见过太多人处理一个几十行的代码块还在那一下一下按dd按到手麻或者在可视模式里选了半天结果选错范围。说实话Vim 的高效不在于你会多少冷门命令而在于你面对删除多行这个高频需求时能不能在几秒钟内选对最合适的那把刀。这篇文章我就把我日常实际在用的八类多行删除技巧完整拆开讲每一类都讲清楚适用场景、执行逻辑和容易踩的坑争取让你看完之后能直接上手用。1. 删除前的底层认知搞清楚 Vim 删除到底做了什么很多 Vim 教程一上来就甩命令列表dd、5dd、dG、:3,8d背得滚瓜烂熟但一旦遇到删错了剪贴板被覆盖宏执行一半跑偏这类问题就完全懵。之所以懵是因为没理解 Vim 删除操作的本质——它不是一个简单的擦除而是一套以寄存器为核心的读写机制。1.1 删除命令的本质剪切到寄存器在 Vim 里执行d开头的任何删除命令内容并不会被直接抛弃而是会被剪切进一个默认寄存器专业术语叫无名寄存器unnamed register用表示。这一点和普通文本编辑器的删除即丢弃有本质区别。也就是说dd删除一整行之后这一行其实还活着保存在内存的寄存器里你可以随时用p把它粘贴到任何位置。这个机制带来两个直接影响。第一删除操作天然具备移动文本的能力——你在文件里删掉一段代码切到另一个文件里p一下内容就过去了这就是跨文件搬移代码块的基本原理。第二它有一个非常容易踩的坑由于所有删除都默认走同一个无名寄存器你只要再执行一次删除之前的寄存器内容就被覆盖了。我经常看到有人删了一行准备去别处粘贴结果中途又随手删了个空格粘贴出来的内容就完全不对了。所以只要你准备删了再粘贴就要养成先选寄存器的习惯。比如用a5dd把5行内容删进寄存器a后面再用ap粘贴。或者用黑洞寄存器_d配合删除相当于只删不存不污染寄存器。这个习惯在批量处理代码时能帮你省掉非常多返工。1.2 数字前缀Vim 命令的乘法器理解了寄存器机制之后另一个必须建立的概念是数字前缀。Vim 的几乎所有命令都由操作符 范围 动作组成而你可以在命令前面加数字让这个命令重复执行 N 次。不要把这个当成死记硬背你可以把它理解成命令的乘法器5dd等于dd执行 5 次删除从当前行开始的连续5行d3G等于删除从当前行到第 3 行之间的全部内容是一个范围操作3dw等于向后删除 3 个单词。数字前缀的关键在于你要搞清楚它修饰的到底是操作符重复次数还是动作范围。5dd是上下文无关的——它就是删5行光标在哪就从哪开始而d5G则是删到第5行为止是一个明确的行号范围。理解了这两类数字的不同语义你在执行删除的时候就会非常清楚自己到底在删什么而不是凭感觉按数字。2. 按行号与范围删除处理我明确知道要删哪些行的场景第一种最刚需的场景是你已经通过某种方式知道了目标行的位置——可能是报错信息告诉你的行号也可能你扫一眼就知道这一段代码是多余的。这时候直接用行号和范围删除效率最高。2.1 绝对行号用:起点,终点d做精确打击如果你知道要删除的起始行和结束行最简单可靠的方式就是行号范围模式。在命令模式下输入:3,8d意思很直白把第 3 行到第 8 行全部删除。这个命令适合所有情况尤其是在文件有明确的行号依据时比如编译器报错、日志定位、别人告诉你把 120 到 130 行删了。这里有个小细节值得注意执行:3,8d之后光标会落在原本第 9 行的位置而不是被删除区域之前。这个行为对连续做多次范围删除非常重要——如果你先删了3,8又想删原来的10,12那现在就不能直接输:10,12d了因为行号已经整体上移了。你在执行完第一次删除后应该先重新确认目标行号或者改用相对当前行的删除方式下面会讲。还有一个扩展用法就是用0和$作为边界。比如:0,$d是清空整个文件不过我一般不建议直接用这个因为一旦清空还没法撤销下面有一节专门讲撤销问题。2.2 相对偏移用:.3,.8d处理光标附近的区域很多时候你知道要删的内容就在光标附近但具体行号需要数一下。这种场景更适合相对地址。Vim 中.代表当前光标所在行你可以用.n表示当前行往下 n 行。:.,.5d这个命令的意思是删除从当前行开始一直到当前行往下 5 行的范围也就是一次删掉 6 行。相比绝对行号它的优势是位置漂移免疫——哪怕文件之前被删过、行号已经变了只要你的光标还停在目标区域附近用相对偏移就永远不出错。再举个例子我经常要删掉当前行到文件末尾的所有内容用:.,$d或者用更快的普通模式命令dGd表示删除G表示跳到文件末尾组合起来就是从当前行删到末尾。同理dgg是删到文件开头。这类组合命令的核心思路是把 d 看成一个操作符把行跳转看成它作用的容器。2.3 可视行模式删之前先看一眼如果你对要删的行范围不是百分之百确定或者你想在删除前有一个确认动作我强烈推荐可视行模式。在普通模式下按下Shift V也就是V会进入按行选择的可视模式此时按j或k上下移动就能逐行扩展高亮选区。选中完成后按d即可删除。这个方式虽然比命令慢一点但它有一个不可替代的价值——高亮选区给了你一个视觉确认的机会。你在代码上上下下一扫立刻就能发现选区边界对不对避免删掉不该删的内容。特别是在处理缩进敏感的代码比如 Python、YAML时可视模式能精确控制到每一行还能看到缩进层级误删率比纯命令低很多。3. 用全局替换思路做连带删除解决多处相同模式的行如果说行号和可视模式解决的是已知位置的删除那么现实中更多情况是我知道要删哪些行但它们在文件里到处都是手动查找太慢。这时候就要用 Vim 的全局命令大杀器了。3.1:g/pattern/d一行命令删掉所有匹配行:g是 Vim 的全局命令它的完整语法是:[范围]g/模式/命令含义可以翻译成在指定范围内默认是全文找到所有匹配模式的行然后对这些行执行命令。配合删除命令d就是最经典的多行删除写法:g/^\s*#/d这条命令会删除所有以任意空格\s*开头的注释行#开头的行。我平时用这个技巧的频率非常高比如清理配置文件里的注释、去掉日志文件里的 DEBUG 输出、删除代码中所有 TODO 注释等等。一次处理几百行完全不是问题。这里有几个非常实用的变体:g/foo/d删除所有包含foo字符串的行:g/^$/d删除所有空行^$匹配开头紧接着结尾也就是空行:g/pattern1\|pattern2/d删除匹配多个模式之一的行注意\|是 Vim 正则里的或。3.2 反向思维:v/pattern/d保留匹配行删掉其他所有行全局命令还有一个反向兄弟:v意思是不匹配的行。:v/pattern/d的语义是删除所有不含某种模式的行。这个反向操作在某些场景下反而更直接。比如你有一个数据文件里面既有有效记录又有大量日志噪声你想只保留带ERROR关键词的行那直接:v/ERROR/d这样除了包含ERROR的行保留以外其他全部删除。相比先思考哪些模式需要删反向操作很多时候更省脑力——你只需要描述留下什么剩下的交给 Vim 删干净。这个思路在写临时分析脚本时尤其有用。我以前处理过一份几千行的服务器访问日志要快速提取所有成功的请求行用:v/200/d一行就清掉了所有非 200 状态码的行。接下来的操作全都基于这个过滤后的缓存文件进行效率和准确率都高得多。3.3 全局命令的边界控制范围参数值得多用很多人不知道:g命令前面是可以加范围的。它并不是只能作用于全文。比如:10,100g/DEBUG/d这条命令只在第 10 行到第 100 行之间查找包含DEBUG的行并删除超出这个范围一律不管。配合相对范围也行:.,.50g/^\s*log\./d从当前行往下 50 行内删除所有以log.开头的行。这个能力在只想清理某个函数内部代码时非常有价值因为你可以先跳到目标函数然后用.当前行加相对范围圈定清理区域避免误伤文件其他部分的同类模式。4. 宏录制批量删除让重复性操作自己跑完前一把刀是模式匹配它处理的是内容格式相同的删除。但现实还有一种情况要被删掉的东西格式各不相同但删除动作有规律——比如删掉当前行再跳到下一个}、删掉当前行并合并到上一行、或者删除 5 行后移到下一段的开头。这种结构性重复用宏录制来处理是最顺手的。4.1 宏的基本姿势录一次用一万次宏录制的流程非常简单核心是三个步骤在普通模式下按q加一个寄存器字母比如qa开始录制执行你要重复的一连串操作可以是任意命令组合比如dd、j、/pattern等再次按q结束录制。之后你只要按a就会回放刚才录制的整段操作。如果你在前面加计数比如100a就会连播 100 次。这时候你可以去泡杯茶Vim 会麻利地处理完剩下的所有重复操作。我举个实际例子假设一个 JSON 文件里每一行前面都有一行格式无关的元数据注释而你现在需要删掉所有注释行。如果注释内容不规律没法用一步正则搞定你就可以录制一个宏0跳到行首dd删掉注释行然后j移到下一行q结束录制。最后看文件行数直接500a一次性清完。4.2 宏的终止条件设计让重复自动停下宏录制最容易忽视的一点是结束条件。很多人录完宏一执行发现它把不该删的东西也删了或者一直滚到了文件末尾还在继续重复操作。这里面有两个关键技巧第一宏里最好包含一个查找动作。比如每次循环末尾都用/pattern跳到下一个目标位置这样当找不到匹配时Vim 会报错并自动停止宏的回放。否则你单纯用j移动宏就会一路执行到文件末尾才停下中间每行都被执行了一遍极容易误删。第二宏里的动作要保证光标位置一致。录制宏时如果你在执行过程中使用了j、w这类相对位移回放到不同行时由于行长度不一样光标落点可能不一致后续命令的执行结果就会跑偏。所以我在录制宏时倾向于每一轮操作都以0跳到行首或^跳到行首非空白字符开始以j或查找跳转结束保证每轮循环的光标初始状态相同。4.3 宏录制中的隐藏坑寄存器与批量回放的问题宏录制的隐藏坑主要在两个方面。一个是寄存器污染问题——你录制宏用的寄存器和你删除操作用的寄存器会冲突。比如你录宏的时候执行过dd那宏体里其实隐含了把删除内容存入无名寄存器这一步。回放多次之后无名寄存器被反复覆盖如果后续还要用p粘贴内容早就变了。所以录制涉及删除的宏时建议在宏体内用_dd删除到黑洞寄存器不污染无名寄存器这样宏整体对寄存器的副作用最小。另一个坑是批量回放时的失控感。100a虽然能一次跑完但如果执行到一半你意识到录制内容有误想中途停掉按Ctrl C可以打断宏回放但这个中断时机可能刚好停在一次操作的中间文件已经处于一个混乱的中间状态。我的经验是宏第一次执行前先:w存个盘执行完以后立刻检查几行关键位置确认无误再继续大批量执行。如果发现问题还能u撤销回来不至于几百行一次性毁掉。5. 操作符 文本对象不靠行号按语义结构删除如果说前面的方法都是按行处理那这一节讲的组合是更符合程序员直觉的按语义处理。Vim 的操作符和文本对象机制允许你直接对一对引号里的内容一个函数体一个标签之间的文字执行删除完全不需要知道内容在哪一行。5.1 核心机制d 文本对象Vim 定义了一套文本对象text objects常见的有w单词s句子p段落(或)一对小括号之间的内容[或]一对中括号之间的内容{或}一对花括号之间的内容一对双引号之间的内容一对单引号之间的内容一对反引号之间的内容tXML/HTML 标签之间的内容。操作符加文本对象的搭配规则是操作符比如d 文本对象。最常见的形式是di(、da(、dip、dit等。i表示 inside内部a表示 around包含配对符或周围内容。举个例子di(把光标放在一对小括号内的任意位置按di(就可以直接删掉括号内的全部内容但保留括号本体。如果你连括号也想一起删掉那就用da(或者da)。同理di删除光标所在位置的双引号内的全部内容dit删除光标所在 HTML 标签内部的文字保留标签外壳。这套东西和行号完全无关你的光标只需要落在那个结构里Vim 会自动识别配对关系非常省心。5.2 结构化语言中的删除利器函数体、代码块一次清空对于写过几天代码的人来说最常用的组合就是di{和da{。比如你在一个函数体内部的某一行想要清空整个函数体直接di{光标不用移动Vim 会自动向上找到函数体的左花括号删除其内部所有代码。如果你想连函数体的花括号一起删掉就按da{。这些操作在处理大段重构时极其高效——你不需要用%去匹配括号也不需要先数行数只需要记住光标在这些括号里面就足够了。再配合gU、gu大小写转换、y复制等操作符这套机制可以扩展出非常多的组合。本质上你在 Vim 里要养成的思维是操作的目标不应该是一行一行数出来的范围而应该是代码本身的语义单元。当你写di(时你表达的是删掉这对括号的语义内容而不是删掉 3 到 15 行。5.3 配对对象删除时容易混淆的i和a很多 Vim 用户会在这里卡住di(和da(到底有什么区别简单记住一句话i是保留外壳删内容a是连同外壳一起删。你可以把i理解成里面insidea理解成周围around。实际使用中的建议是如果目标是清空一个结构、保留占位符用di如果目标是彻底移除这个结构用da。用di(清空函数的参数列表保留括号用da(删除整个参数列表包括括号语义区别一目了然。比如你在写一个函数调用发现参数全部写错了想重写di(正好清空参数区的所有内容光标还会停在括号之间你可以直接输入新的参数。这个操作流程比先选中再删除再重新进入插入模式要快上一个数量级。6. 构建一个快速删除的肌肉记忆常用快捷键速查与效率排序讲了那么多机制和原理最终要落到日常操作上。我从实际使用中总结了一套效率排序按场景优先级排列对照自己平时最常用的删除需求能帮你快速形成肌肉记忆。6.1 按场景分类的快捷命令速查场景推荐命令说明删除当前行dd最简单最常用删除连续 N 行Ndd如5dd数字前缀直接删除当前行到文件末尾dG适合清空后半部分删除当前行到文件开头dgg适合删除文件头部删除指定范围:起点,终点d明确行号时用删除光标附近一定范围:.,.Nd相对偏移安全删除所有匹配模式的行:g/pattern/d大扫除删除所有不匹配模式的行:v/pattern/d只保留目标删除花括号内全部di{函数体快速清空删除花括号连同所有内容da{连外壳一起删删除引号内全部di修改字符串值删除空行:g/^$/d格式化常用删除注释行:g/^\s*#/dPython/Shell 文件常用6.2 效率排序的底层逻辑这个排序的核心逻辑是能用语义解决的就不数行能用范围解决的就别用可视模式能用全局命令的别手动选中。比如你要清空某个函数体di{永远比你切到可视模式选择 30 行再删快得多你要清理一个文件的所有注释g/^\s*#/d永远比手动一屏一屏删快得多。反过来如果你的光标定位不准确、对文件结构不熟悉那优先用可视化确认或者范围命令避免搞错。高效的基础不是手速快而是每次操作前都能快速判断用哪把刀最准。7. 我踩过的坑这些操作比想象中更容易翻车技巧讲完该聊聊事故了。Vim 多行删除看着简单实际使用中的翻车案例我见得太多了。下面几个坑是我自己踩过或者帮别人排查过的高频问题。7.1 撤销失灵:g/pattern/d之后按u却回不去很多人执行:g/pattern/d删了一大堆行之后发现按u撤销竟然没用文件被永久删改。这其实是对 Vim 命令历史的误解。u撤销的是普通模式下的修改而:g/这类 Ex 命令的执行有些情况下会被当作一次操作有些情况则不一定能完整撤销取决于你执行了几次、有没有触发某些插件行为。我的经验是执行任何批量删除:g、:v、宏批量回放之前先留下退路。最简单的方式是执行前按:w存一次盘万一删错了直接:e!重新加载文件。另外一个更稳妥的习惯是用:earlier 10m——这条命令可以把整个缓冲区恢复到 10 分钟之前的状态比u的普通撤销粒度大得多适合批量危险操作后的整体回滚。7.2 宏回放的光标漂移导致误删宏执行到后面越来越乱最常见的原因是录制时用了j或w这类相对位移导致第二次播放在不同长度的行上光标落点不一致后续命令就全乱了。我的规避方案已经提过每轮循环都以0或^开始让光标回到一个确定的位置循环末尾用查找命令跳到下一处目标。另外如果录制内容里包含$行尾在长短不一的文本里也非常容易导致光标漂移能尽量避免就尽量避免。7.3 可视模式的误选默认包含行尾换行符可视行模式ShiftV选中内容后按d删除的是一整行连同换行符。这个行为的后果是你删除后下一行会朝上顶回来有时候视觉上看起来像只删了半行实际已经删掉整个逻辑行。另一种误操作是你用v按字符选择精确选中了一段但包住了一个换行符删出来的结果可能把两行合并成一行。遇到这种情况我的建议是先观察被删位置的前后行内容如果发现意外合并立刻u撤销再进入可视模式重新选精确范围。7.4 大文件批量删除的卡顿假象还有一个不能忽视的坑是对超大文件执行:g/pattern/d或宏批量删除时Vim 可能会短暂卡顿。这是因为全局命令需要对每一行做正则匹配在几万行的文件里这个开销确实不低。我处理过一份 10 万行的日志文件执行:g/ERROR/vim的某个变体时界面直接假死几秒但文件其实还在处理只是 UI 暂时无响应。遇到这种情况千万不要狂按键盘给它几秒钟等命令执行完毕。如果你需要频繁在大文件里做这种过滤可以考虑先用sed在外部处理再重新载入文件:%!sed /pattern/d把重活交给流编辑器速度和稳定性都好很多。8. 综合实战用多种技巧组合完成一次代码清理技巧讲得再多不如一个完整场景串联起来。下面我模拟一个非常常见的需求带你把前面几种方法组合使用一遍。8.1 任务描述清理一个历史遗留的 Python 文件假设我现在打开了一个 Python 文件它长这样简化示意import os import sys # TODO: remove this DEBUG_FLAG True def main(): # 调试输出 print(start init) data load_data() # 临时处理 data.clean() if DEBUG_FLAG: print(data loaded:, len(data)) # 输出最终结果 print(done) return data # old helper, not used anymore def old_helper_1(): pass # old helper, not used anymore def old_helper_2(): pass def load_data(): ...我的目标是删除所有#注释行删除DEBUG_FLAG相关定义和条件删除删除两个不再使用的old_helper函数保留其他所有代码。8.2 实操步骤拆解第一步删除所有注释行。这个文件不大我可以直接用力删掉所有注释行:g/^\s*#/d这一步会把四行#开头的注释全部删除文件立刻清爽很多。第二步删除DEBUG_FLAG相关内容。这里有两种删除方式。如果DEBUG_FLAG的定义那两行明确我可以直接用:g/DEBUG_FLAG/d删除所有包含这个标识的行。由于这个标识在定义和if DEBUG_FLAG:中都出现一行命令就能把两个相关行一起干掉不用手动定位和分步删除。:g/DEBUG_FLAG/d第三步删除整个old_helper_1函数。这两个函数体内部有代码示意中用pass占位如果只删除函数名那一行pass就会残留。所以正确的方式是使用文本对象删除把光标移到old_helper_1的def行内部然后执行da{这会连同函数体一起删掉。同样处理old_helper_2。如果函数体很大da{的效率远高于一行行删除。第四步清理可能的空行。经过删除后原先注释行所在的位置可能留下空白行直接删掉所有空行:g/^$/d最终文件里的核心结构就干净了。8.3 从组合使用中提炼的实操经验这个例子看起来很常规但它正好串联了:g模式删除、文本对象删除、空行清理三类技巧。我的经验是清理一个杂乱文件时先用全局模式做面上的过滤再用文本对象做点上的深挖最后补一次空行清理做收尾。这个顺序能最大程度减少手动定位次数也避免你在面和点之间反复横跳。类似地如果你要处理的是一个重要文件记得在开始组合删除前先:w temp_copy存一个副本。清理完确认无误后再把副本删掉。这套先留后手再动手的习惯救过我很多次。写在最后的一点个人建议关于 Vim 的多行删除工具本身并不复杂真正难得的其实是形成一种删除思维:先判断你要删的东西是模式、是结构、是范围还是无规律的重复再决定用哪一把刀。大多数人在 Vim 上效率提不上去不是命令背少了而是不管什么场景都在用同一招。如果你今天只记住一件事那就记住:g/pattern/d和di{这两个组合。一个解决所有重复模式行的清理一个解决所有结构体的清空覆盖了我日常 60% 以上的多行删除需求。等你把这两个用熟了再回头看dd连按这种操作自然就会觉得笨重了。最后分享一个我自己的小技巧如果你不确定某个删除命令的行为先在文件末尾一行空行上试一下或者用:set nohlsearch关掉高亮之后用:%s/.*//这类无害命令做实验。Vim 的容错机制并不算强但只要你习惯小步快跑、频繁保存、留好备份它就是你手上最趁手的编辑器多行删除这关过了剩下的 Vim 使用体验会顺非常多。
返回列表