¿Cómo fusionar dos bloques de texto de varias líneas en Vim?

Resuelto ThiefMaster asked hace 12 años • 10 respuestas

Me gustaría fusionar dos bloques de líneas en Vim, es decir, tomar las líneas  kl y agregarlas a las líneas mn . Si prefiere una explicación en pseudocódigo: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)].

Por ejemplo,

abc
def
...

123
45
...

debe convertirse

abc123
def45

¿Existe una buena manera de hacer esto sin copiar y pegar manualmente línea por línea?

ThiefMaster avatar May 26 '12 02:05 ThiefMaster
Aceptado

Ciertamente puedes hacer todo esto con una sola copia y pegado (usando la selección en modo de bloque), pero supongo que eso no es lo que quieres.

Si quieres hacer esto solo con comandos Ex

:5,8del | let l=split(@") | 1,4s/$/\=remove(l,0)/

se transformará

work it 
make it 
do it 
makes us 
harder
better
faster
stronger
~

en

work it harder
make it better
do it faster
makes us stronger
~

ACTUALIZACIÓN: Una respuesta con tantos votos positivos merece una explicación más detallada.

En Vim, puede usar el carácter de barra vertical ( |) para encadenar múltiples comandos Ex, por lo que lo anterior es equivalente a

:5,8del
:let l=split(@")
:1,4s/$/\=remove(l,0)/

Muchos comandos Ex aceptan un rango de líneas como argumento de prefijo; en el caso anterior, antes 5,8de dely 1,4antes de s///especifican en qué líneas operan los comandos.

delelimina las líneas dadas. Puede tomar un argumento de registro, pero cuando no se proporciona ninguno, vuelca las líneas al registro sin nombre, @"tal como lo hace la eliminación en modo normal. let l=split(@")luego divide las líneas eliminadas en una lista, usando el delimitador predeterminado: espacios en blanco. Para trabajar correctamente en entradas que tenían espacios en blanco en las líneas eliminadas, como:

more than 
hour 
our 
never 
ever
after
work is
over
~

necesitaríamos especificar un delimitador diferente para evitar que "el trabajo es" se divida en dos elementos de la lista: let l=split(@","\n").

Finalmente, en la sustitución s/$/\=remove(l,0)/, reemplazamos el final de cada línea ( $) con el valor de la expresión remove(l,0). remove(l,0)altera la lista l, eliminando y devolviendo su primer elemento. Esto nos permite reemplazar las líneas eliminadas en el orden en que las leemos. En su lugar, podríamos reemplazar las líneas eliminadas en orden inverso usando remove(l,-1).

rampion avatar May 25 '2012 19:05 rampion

Se puede obtener un comando Ex elegante y conciso que resuelva el problema combinando los comandos :global, :movey :join. Suponiendo que el primer bloque de líneas comienza en la primera línea del búfer y que el cursor está ubicado en la línea inmediatamente anterior a la primera línea del segundo bloque, el comando es el siguiente.

:1,g/^/''+m.|-j!

Para obtener una explicación detallada de esta técnica, consulte mi respuesta a esencialmente la misma pregunta " ¿Cómo lograr el comportamiento "pegar -d '␣'" listo para usar en Vim? ”.

ib. avatar May 26 '2012 07:05 ib.

Para unir bloques de línea, debes realizar los siguientes pasos:

  1. Vaya a la tercera línea:jj
  2. Ingrese al modo de bloqueo visual:CTRL-v
  3. Ancle el cursor al final de la línea (importante para líneas de diferente longitud):$
  4. Ve al final:CTRL-END
  5. Cortar el bloque:x
  6. Ir al final de la primera línea:kk$
  7. Pega el bloque aquí:p

El movimiento no es el mejor (no soy un experto), pero funciona como querías. Espero que haya una versión más corta.

Estos son los requisitos previos para que esta técnica funcione bien:

  • Todas las líneas del bloque inicial (en el ejemplo de la pregunta abcy def) tienen la misma longitud XOR
  • la primera línea del bloque inicial es la más larga y no te importan los espacios adicionales entre medio) XOR
  • La primera línea del bloque inicial no es la más larga y hay espacios adicionales hasta el final.
mliebelt avatar May 25 '2012 19:05 mliebelt

Así es como lo haría (con el cursor en la primera línea):

qama:5<CR>y$'a$p:5<CR>dd'ajq3@a

Necesitas saber dos cosas:

  • El número de línea en el que comienza la primera línea del segundo grupo (5 en mi caso), y
  • el número de líneas en cada grupo (3 en mi ejemplo).

Esto es lo que está pasando:

  • qaregistra todo hasta el siguiente qen un "búfer" en formato a.
  • macrea una marca en la línea actual.
  • :5<CR>pasa al siguiente grupo.
  • y$tira del resto de la línea.
  • 'avuelve a la marca establecida anteriormente.
  • $ppega al final de la línea.
  • :5<CR>regresa a la primera línea del segundo grupo.
  • ddlo elimina.
  • 'avuelve a la marca.
  • jqbaja una línea y deja de grabar.
  • 3@arepite la acción para cada línea (3 en mi caso)
Kenneth Ballenegger avatar May 26 '2012 00:05 Kenneth Ballenegger

Como se mencionó en otra parte, la selección de bloques es el camino a seguir. Pero también puedes utilizar cualquier variante de:

:!tail -n -6 % | paste -d '\0' % - | head -n 5

Este método se basa en la línea de comando de UNIX. La pasteutilidad fue creada para manejar este tipo de combinación de líneas.

PASTE(1)                  BSD General Commands Manual                 PASTE(1)

NAME
     paste -- merge corresponding or subsequent lines of files

SYNOPSIS
     paste [-s] [-d list] file ...

DESCRIPTION
     The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
     and writes the resulting lines to standard output.  If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
     it were an endless source of empty lines.
kevinlawler avatar May 26 '2012 04:05 kevinlawler