Vim全局命令匹配多行合并失败问题求助及优化方案咨询
问题:Vim全局命令合并非acc行到上方最近acc行的问题
问题场景
我有如下格式的文件:
acc abcd etc. etc. // line 1 // line 2 acc more words // 3-4 more lines acc some other words
文件里有两种行:以acc开头的行,和非acc开头的行。我想把所有非acc行合并到它上方最近的acc行后面,用@@分隔,处理后只保留acc开头的行,最终效果如下:
acc abcd etc. etc.@@//line 1@@//line 2 acc more words@@//3-4 more lines acc some other words
我试了这个全局命令来分组匹配行并替换换行符:
:%g/^acc.*\_.\{-}\(^acc\)\@=/s/\n/@@/g
但它只替换了每个匹配块里的第一个换行符,请问这命令哪里错了?有没有更简单的实现方式?
你的命令问题出在哪?
核心原因是Vim的:g(global)命令的工作逻辑和你想的不一样::g/pattern/command的运行流程是:先找到所有能匹配pattern的行(注意,哪怕你的pattern是多行匹配,:g也只会把匹配块的第一行当作目标行),然后对每一行执行后面的command。
你用的s/\n/@@/g默认只在当前单行内查找替换换行符,但单行里根本没有换行——实际上它替换的是当前acc行末尾的那个换行(也就是和下一行的分隔),而后面的非acc行之间的换行,因为不在当前处理的行里,所以根本没被触及,自然只改了第一个换行符。
靠谱的解决方案
方法1:改进Vim全局命令(纯Vim操作)
可以用:g配合:normal命令,手动循环合并后续行:
:%g/^acc/exec "normal! J" | while getline('.')[0:2] !=# 'acc' && line('.') < line('$') | exec "normal! s/\n/@@/" | exec "normal! J" | endwhile
逻辑拆解:
- 找到所有以
acc开头的行 - 先执行
J合并下一行(默认用空格分隔,之后替换成@@) - 循环检查:当前行的下一行是不是非
acc开头,且不是最后一行 - 每次循环把换行符替换成
@@,再合并下一行,直到遇到下一个acc行或文件末尾
方法2:多行替换模式(更简洁的Vim写法)
用Vim的\v(very magic)模式简化正则,直接批量替换:
:%s/\v^((acc.*)\n(^(?!acc).*$\n?)+)/\2@@\=substitute(submatch(3), '\n', '@@', 'g')/ :%s/\v^acc.*\n$/\0/
逻辑说明:
- 第一行:匹配
acc行 + 后面所有非acc行的组合,用substitute把非acc行之间的换行替换成@@,再和acc行拼接 - 第二行:处理最后一行没有后续行的
acc行,保持原样(避免被误处理)
方法3:用awk工具(高效又直观)
如果你的环境支持awk,这个方法会比纯Vim操作更简单:
awk '/^acc/ {if (prev != "") print prev; prev=$0; next} {prev=prev"@@"$0} END {print prev}' your_file.txt
逻辑超级清晰:
- 遇到
acc开头的行:如果之前缓存了内容(prev),先打印出来;然后把当前行设为新的prev,跳过后续处理 - 遇到非
acc行:直接把当前行拼接到prev后面,用@@分隔 - 文件处理完后,打印最后剩下的
prev内容
内容的提问来源于stack exchange,提问作者thorondor1990
相关产品推荐
相关产品推荐

