如何从 VIM 中的字符串列表中进行替换?

发布于 2024-08-12 21:44:21 字数 322 浏览 5 评论 0原文

我是 vim 用户,我希望在替换时能够循环一系列子字符串。我怎样才能使用一些 vim 魔法从一组这样的行转到:

Afoo
Bfoo
Cfoo
Dfoo

Abar
Bbar
Cbaz
Dbaz

我想从头开始搜索文件中下一次出现的 foo,并将前两个实例替换为 bar,将后两个实例替换为 baz.使用 for 循环是最好的选择吗?如果是这样,那么如何在替换命令中使用循环变量?

I am a vim user, and I want to be able to loop over a range of substrings when I am substituting. How can I use some vim magic to go from a set of lines like this:

Afoo
Bfoo
Cfoo
Dfoo

to

Abar
Bbar
Cbaz
Dbaz

?

I want to search my file from the start for the next occurance of foo, and replace the first two instances with bar, and the second two with baz. Is using a for loop the best option? If so, then how do I use the loop variable in the substitution command?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

故事未完 2024-08-19 21:44:21

我将使用一个具有状态的函数,并从 %s 调用该函数。类似于:

" 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'])/

如果您愿意,可以将对两个命令的调用封装到单个命令中。


编辑:这是一个集成为命令的版本:

  • 接受我们希望的任意数量的替换,所有替换都需要用分隔符分隔;
  • 支持反向引用;
  • 只能替换 N 个第一次出现,N == 如果命令调用被敲击(带有!)则指定的替换次数
  • 不支持 g、i 等常见标志(: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

I would use a function that has a state, and call this function from %s. Something like:

" 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

And use them with:

:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/

The call to the two commands could be encapsulated into a single command if you wish.


EDIT: Here is a version integrated as a command that:

  • accepts as many replacements as we wish, all the replacements needs to be separated with the separator-character ;
  • supports back-references ;
  • can replace only the N first occurrences, N == the number of replacements specified if the command call is banged (with a !)
  • does not support usual flags like g, i (:h :s_flags) -- for that, we would have for instance to impose the command call to always ends up with a / (or whatever separator-character), if not the last text is interpreted as flags.

Here is the command definition:

: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

which could be used this way:

:%RotateSubstitute#foo#bar#bar#baz#baz#

or even, considering the initial text:

AfooZ
BfooE
CfooR
DfooT

the command

%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/

would produce:

ZbarA
BbarE
RbarC
DbarT
獨角戲 2024-08-19 21:44:21

严格来说,这并不是您想要的,但对循环很有用。

我写了一个插件 swapit http://www.vim.org/scripts /script.php?script_id=2294 其中可以帮助循环遍历字符串列表。例如。

 :Swaplist foobar foo bar baz

然后输入

 This line is a foo

创建一个简单的复制/粘贴行,转到最后一个单词并按 ctrl-a 交换。

 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} 敏感。

This is Not strictly what you want but can be useful for cycles.

I've written a plugin swapit http://www.vim.org/scripts/script.php?script_id=2294 which among other things can help with cycling through lists of strings. Eg.

 :Swaplist foobar foo bar baz

then type

 This line is a foo

create a simple yank/paste line, go to last word and ctrl-a swap.

 qqyyp$^A

then execute the swap pattern

 100@q

to get

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
...

It could probably be applied to your problem although its {cword} sensitive.

孤檠 2024-08-19 21:44:21

为什么不呢:

:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/

我不确定它是否涵盖了问题的广度,但确实具有作为简单替代品的优点。如果这个解决方案不能解决,则更复杂的解决方案可能会涵盖该解决方案。

Why not:

:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/

I'm not sure that it covers the breadth of the problem but does have the virtue of being a simple substitute. A more complex one may cover the solution if this one doesn't.

弥繁 2024-08-19 21:44:21

这就是我尝试该宏的方式。

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.

欢迎任何有关如何做得更好的建议。

谢谢,
马丁.

This is how I'd attempt that macro.

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.

Any advice on how to do it better is welcome.

thanks,
Martin.

鸩远一方 2024-08-19 21:44:21

录制一个可以替换前两个的宏,然后使用 :s 来完成其余的可能会更容易。

该宏可能类似于 /foo^Mcwbar^[。如果您不熟悉宏模式,只需按 qa (存储它的寄存器),然后按击键 /foo即可。 cwbar

现在,一旦您获得了该宏,请执行 2@a 来替换当前缓冲区中的前两个匹配项,并通常使用 :s 来替换其余部分。

It's probably going to be much easier to record a macro that can replace the first two, and then use :s for the rest.

The macro might look like /foo^Mcwbar^[. If you're not familiar with macro mode, just hit q, a (the register to store it in) and then the keystrokes /foo <Enter> cwbar <Escape>.

Now once you've got that macro, do 2@a to replace the first two occurrences in the current buffer and use :s normally to replace the rest.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文