我是一个vim用户,我希望能够在替换时循环一系列子串。我如何使用一些vim魔法来从这样的一组行:
Afoo
Bfoo
Cfoo
Dfoo
到
Abar
Bbar
Cbaz
Dbaz
我想从一开始就搜索我的文件,以便foo
出现下一次,并将前两个实例替换为bar
,将后两个实例替换为baz
。使用for循环是最好的选择吗?如果是这样,那么如何在替换命令中使用循环变量?
答案 0 :(得分:9)
我会使用一个具有状态的函数,并从%s调用此函数。类似的东西:
" untested code
function! InitRotateSubst()
let s:rs_idx = 0
endfunction
function! RotateSubst(list)
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
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)
" echom cmd
" and run it
exe cmd
endfunction
function! s:DoRotateSubst(do_loop, list, replaced, ...)
" 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
答案 1 :(得分:2)
这不是你想要的严格意义,但可以用于循环。
我已经编写了一个插件swapit http://www.vim.org/scripts/script.php?script_id=2294,其中包括循环查看字符串列表。例如
:Swaplist foobar foo bar baz
然后输入
This line is a foo
创建一个简单的抽动/粘贴行,转到最后一个单词并按ctrl-a swap。
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}敏感,但它可能适用于您的问题。
答案 2 :(得分:2)
为什么不:
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
我不确定它是否涵盖问题的广度,但确实具有作为简单替代品的优点。如果没有,那么更复杂的解决方案可能会覆盖解决方案。
答案 3 :(得分:1)
这就是我尝试使用宏的方法。
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.
欢迎任何有关如何做得更好的建议。
感谢, 马丁。
答案 4 :(得分:0)
记录一个可以替换前两个的宏可能要容易得多,然后再使用:s。
宏可能看起来像/foo^Mcwbar^[
。如果您不熟悉宏模式,只需点击q
,a
(存储它的寄存器),然后按键/foo <Enter> cwbar <Escape>
。
现在,一旦你有了这个宏,你可以2@a
替换当前缓冲区中的前两个匹配项,并正常使用:s
替换其余的。