Pregunta

Soy un usuario de vim, y quiero poder recorrer un rango de subcadenas cuando estoy sustituyendo. ¿Cómo puedo usar un poco de magia vim para ir de un conjunto de líneas como esta:

Afoo
Bfoo
Cfoo
Dfoo

a

Abar
Bbar
Cbaz
Dbaz

?

Quiero buscar mi archivo desde el principio para la próxima aparición de foo, y reemplazar las dos primeras instancias con bar, y las dos últimas con baz. ¿Usar un bucle for es la mejor opción? Si es así, ¿cómo uso la variable de bucle en el comando de sustitución?

¿Fue útil?

Solución

Usaría una función que tiene un estado y llamaría a esta función desde% s. Algo así como:

" 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

Y úselos con:

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

La llamada a los dos comandos podría encapsularse en un solo comando si lo desea.


EDITAR: Aquí hay una versión integrada como un comando que:

  • acepta tantos reemplazos como queramos, todos los reemplazos deben separarse con el carácter separador;
  • admite referencias anteriores;
  • puede reemplazar solo las N primeras apariciones, N == la cantidad de reemplazos especificados si la llamada de comando es golpeada (con un!)
  • no admite marcas habituales como g, i (:h :s_flags); para eso, por ejemplo, tendríamos que imponer la llamada de comando para que siempre termine con un / (o cualquier carácter separador), si no el el último texto se interpreta como banderas.

Aquí está la definición del comando:

: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

que podría usarse de esta manera:

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

o incluso, considerando el texto inicial:

AfooZ
BfooE
CfooR
DfooT

el comando

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

produciría:

ZbarA
BbarE
RbarC
DbarT

Otros consejos

Esto no es estrictamente lo que desea, pero puede ser útil para los ciclos.

He escrito un intercambio de complementos http: //www.vim. org / scripts / script.php? script_id = 2294 que, entre otras cosas, puede ayudar a recorrer las listas de cadenas. Ej.

 :Swaplist foobar foo bar baz

luego escriba

 This line is a foo

crea una línea simple de tirar / pegar, ve a la última palabra y ctrl-a swap.

 qqyyp$^A

luego ejecute el patrón de intercambio

 100@q

para obtener

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

Probablemente podría aplicarse a su problema, aunque es {cword} sensible.

¿Por qué no?

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

No estoy seguro de que cubra la amplitud del problema, pero tiene la virtud de ser un simple sustituto. Una más compleja puede cubrir la solución si esta no lo hace.

Así es como intentaría esa 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.

Cualquier consejo sobre cómo hacerlo mejor es bienvenido.

gracias, Martin.

Probablemente será mucho más fácil grabar una macro que pueda reemplazar a las dos primeras y luego usar: s para el resto.

La macro podría verse como /foo^Mcwbar^[. Si no está familiarizado con el modo macro, simplemente presione q, a (el registro para almacenarlo) y luego presione las teclas /foo <Enter> cwbar <Escape>.

Ahora, una vez que tenga esa macro, haga 2@a para reemplazar las dos primeras ocurrencias en el búfer actual y use :s normalmente para reemplazar el resto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top