我是vim用户,当我进行替换时,希望能够循环遍历一系列子字符串。如何使用vim魔法从这样的一组行中进行操作:
Afoo
Bfoo
Cfoo
Dfoo
to
Abar
Bbar
Cbaz
Dbaz
我想从文件开头开始搜索下一个出现的foo
,并将前两个实例替换为bar
,第二个两个实例替换为baz
。使用for循环是最好的选择吗?如果是,那么如何在替换命令中使用循环变量?
我是vim用户,当我进行替换时,希望能够循环遍历一系列子字符串。如何使用vim魔法从这样的一组行中进行操作:
Afoo
Bfoo
Cfoo
Dfoo
to
Abar
Bbar
Cbaz
Dbaz
我想从文件开头开始搜索下一个出现的foo
,并将前两个实例替换为bar
,第二个两个实例替换为baz
。使用for循环是最好的选择吗?如果是,那么如何在替换命令中使用循环变量?
" untested code
function! InitRotateSubst() abort
let s:rs_idx = 0
endfunction
function! RotateSubst(list) abort
let res = a:list[s:rs_idx]
let s:rs_idx += 1
if s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
return res
endfunction
并将它们与以下内容一起使用:
:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/
:h :s_flags
)-- 为了实现这个功能,我们需要强制命令调用始终以 /(或其他分隔符字符)结尾,否则最后的文本将被解释为标志。:command! -bang -nargs=1 -range RotateSubstitute <line1>,<line2>call s:RotateSubstitute("<bang>", <f-args>)
function! s:RotateSubstitute(bang, repl_arg) range abort
let do_loop = a:bang != "!"
" echom "do_loop=".do_loop." -> ".a:bang
" reset internal state
let s:rs_idx = 0
" obtain the separator character
let sep = a:repl_arg[0]
" obtain all fields in the initial command
let fields = split(a:repl_arg, sep)
" prepare all the backreferences
let replacements = fields[1:]
let max_back_ref = 0
for r in replacements
let s = substitute(r, '.\{-}\(\\\d\+\)', '\1', 'g')
" echo "s->".s
let ls = split(s, '\\')
for d in ls
let br = matchstr(d, '\d\+')
" echo '##'.(br+0).'##'.type(0) ." ~~ " . type(br+0)
if !empty(br) && (0+br) > max_back_ref
let max_back_ref = br
endif
endfor
endfor
" echo "max back-ref=".max_back_ref
let sm = ''
for i in range(0, max_back_ref)
let sm .= ','. 'submatch('.i.')'
" call add(sm,)
endfor
" build the action to execute
let action = '\=s:DoRotateSubst('.do_loop.',' . string(replacements) . sm .')'
" prepare the :substitute command
let args = [fields[0], action ]
let cmd = a:firstline . ',' . a:lastline . 's' . sep . join(args, sep) . sep . 'g'
" echom cmd
" and run it
exe cmd
endfunction
function! s:DoRotateSubst(do_loop, list, replaced, ...) abort
" echom string(a:000)
if ! a:do_loop && s:rs_idx == len(a:list)
return a:replaced
else
let res0 = a:list[s:rs_idx]
let s:rs_idx += 1
if a:do_loop && s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
let res = ''
while strlen(res0)
let ml = matchlist(res0, '\(.\{-}\)\(\\\d\+\)\(.*\)')
let res .= ml[1]
let ref = eval(substitute(ml[2], '\\\(\d\+\)', 'a:\1', ''))
let res .= ref
let res0 = ml[3]
endwhile
return res
endif
endfunction
:%RotateSubstitute#foo#bar#bar#baz#baz#
AfooZ
BfooE
CfooR
DfooT
%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/
会产生:
ZbarA
BbarE
RbarC
DbarT
这不是您严格要求的,但对于循环可能会有用。
我编写了一个名为swapit的插件http://www.vim.org/scripts/script.php?script_id=2294,可以帮助循环遍历字符串列表。例如:
:Swaplist foobar foo bar baz
This line is a foo
qqyyp$^A
然后执行交换模式
100@q
获取
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
...
虽然它比较{cword}敏感,但它很可能适用于您的问题。
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
我不确定它是否涵盖了问题的广度,但它具有简单替代品的优点。如果这个简单的替代方案不行,一个更复杂的可能会解决问题。
这是我尝试编写该宏的方式。
qa Records macro in buffer a
/foo<CR> Search for the next instance of 'foo'
3s Change the next three characters
bar To the word bar
<Esc> Back to command mode.
n Get the next instance of foo
. Repeat last command
n Get the next instance of foo
3s Change next three letters
baz To the word bar
<Esc> Back to command mode.
. Repeat last command
q Stop recording.
1000@a Do a many times.
欢迎提供任何改进建议。
谢谢, 马丁。
.
前面缺少了一个 n
。 - Rich记录一个可以替换前两个的宏可能会更容易,然后使用 :s 替换其余部分。
该宏可能看起来像 /foo^Mcwbar^[
。如果您不熟悉宏模式,请按下 q
,a
(要存储宏的寄存器),然后按下键盘上的 /foo <Enter> cwbar <Escape>
。
现在一旦您拥有了该宏,请执行 2@a
来替换当前缓冲区中的前两个出现次数,并正常使用 :s
替换其余部分。