¿Cómo invertir el orden de las líneas?

24

¿Cómo puedo invertir el orden de las líneas para que la primera línea aparezca al final y la última línea aparezca primero? (Podrían ser todas las líneas en un búfer, un rango de direcciones o una selección de modo visual lineal).

Me gustaria transformar

rat
ox
tiger
⋮
dog
pig

dentro

pig
dog
⋮
tiger
ox
rat

sin recurrir a un comando externo como tac.

200_success
fuente
¿Alguna sugerencia para mejores etiquetas en esta pregunta?
200_success
1
¿quizás una nueva etiqueta 'pure-vi' o similar? He visto varias preguntas que se beneficiarían de una etiqueta que indicaría un deseo de no tener herramientas externas involucradas. ¿Debo preguntar sobre eso en Meta?
John O'M.
1
@Carpetsmoker (y cualquier otra persona interesada en seguir esto) la pregunta de la etiqueta ahora está en meta meta.vi.stackexchange.com/questions/1229/…
John O'M.

Respuestas:

29

El poder de lo global funcionará aquí:

:g/^/exe "normal ddggP"

O, más simplemente (gracias @tommcdo)

:g/^/move 0

El primero coincidirá con cada línea y para cada línea, elimínelo y péguelo en la parte superior del archivo. A medida que se mueve por el archivo, invierte el texto.

El segundo coincide de manera similar con cada línea y la mueve a la parte superior del archivo.

Nota: Ambos funcionan en todo el archivo y no se aplicarán correctamente para invertir un subconjunto de líneas. Vea la respuesta de Ingo Karkat para una solución que funciona dentro de un rango.

Descripción:

gel comando global
/^/coincide con cualquier línea que tenga un comienzo (es decir, todas las líneas)
exeejecute la siguiente cadena
"normalrealice los comandos en modo normal
ddelimine la línea
ggmueva a la parte superior del archivo
Ppegue por encima de la posición actual

move 0 mueve la línea actual a debajo de la línea 0 (que la coloca en la posición 1 o la primera línea del archivo)

John O'M.
fuente
66
En lugar del :normalcomando, podemos usar el comando Ex :move 0, que mueve la línea al comienzo del búfer.
tommcdo
1
También :executesolo es necesario cuando el comando debe construirse dinámicamente, por ejemplo :execute 'normal' g:user_command.
tommcdo
@tommcdo buenos puntos! Tengo la costumbre de usar :executeporque a menudo termino agregando otros comandos Ex después del existente más tarde, y es más conveniente para mí tener el :exeallí ya que tener que volver e insertarlo más tarde. Desafortunadamente, ese hábito se filtró en esta respuesta donde no se aplica tanto.
John O'M.
1
Más explicación sobre mi uso de :execute: dado que toma una cadena, proporciona una delimitación clara de dónde terminan los comandos en modo normal, aunque no estoy construyendo la cadena, es más fácil para mí encontrar comillas equilibradas que buscar <esc>o lo que sea para terminar el modo. Nuevamente, esto es preferencia personal y hábito. :-)
John O'M.
3
Esto funcionará para un rango por cierto: :9,11g/^/move 8... El último número debe ser el comienzo del rango menos 1 (adaptado de la respuesta de Ingo).
Martin Tournoij
13

Esta línea (para su ~/.vimrc) define un :Reversecomando; También puede usar la :globalparte directamente, pero la sintaxis de :move(que desplaza iterativamente las líneas antes del comienzo del rango, invirtiéndola) no es fácil de memorizar:

:command! -bar -range=% Reverse <line1>,<line2>global/^/m<line1>-1
Ingo Karkat
fuente
1
Como FYI para los lectores, los <line1>& <line2>están obligados a hacer que esto funcione en un rango, es decir: :7,9Reverse(son características de command, no globalo move). El más simple :command! -bar -range=% Reverse :global/^/m 0también funcionará, pero solo para todo el búfer ...
Martin Tournoij
6

Vim puro:

:g/^/m0

Explicación:

De acuerdo con :help multi-repeat, :gy su primo :vtrabajan de una manera de dos pasos.

La primera pasada :gmarca cada coincidencia de línea {pattern}, mientras que la segunda pasada (aparentemente realizada comenzando al principio del archivo y continuando hasta el final) realiza la [cmd]. El uso anterior de :gaprovecha el orden en que se procesan las líneas (lo que probablemente esté bien, aunque probablemente no esté técnicamente garantizado).

Funciona marcando primero cada línea, luego moviendo la primera línea marcada a la parte superior del archivo, luego moviendo la segunda a la parte superior del archivo (arriba de la línea movida previamente), luego la tercera línea marcada (nuevamente arriba de la movida previamente línea), y así sucesivamente hasta que la última línea del archivo se mueva a la parte superior, invirtiendo efectivamente el archivo.

Tenga en cuenta que si las :glíneas procesadas en cualquier orden que no sea de arriba a abajo, este comando no funcionaría.

Fuente: Invierte todas las líneas y potencia de g en vim wikia.

Pocos ejemplos con comandos externos:

  • tac(parte de GNU coreutils - catinvertido):

    :%!tac                                                                                                                                                                                                                                                              
    
  • tail en BSD / OSX (no compatible con POSIX):

    :%!tail -r
    

    -r La opción -r hace que la entrada se muestre en orden inverso, por línea.

    Consulta: man tarpara más detalles.

Para más ideas, ver:

kenorb
fuente
2
¿No es lo :g/^/m0mismo que :g/^/move 0, cuál es la respuesta de John?
muru
@muru Creo que sí, pero este es más corto (según vim wikia) y he agregado una explicación diferente con algunos ejemplos adicionales de uso de líneas de comando.
kenorb
Sí, voté por los otros comandos (también vine a publicar tac). Pero sospecho que el voto negativo se debió a que la respuesta se repitió.
muru
Soy consciente de que tacOP lo mencionó, pero todas las demás preguntas similares serían duplicadas de todos modos, por lo que es bueno mencionarlo nuevamente. John tomó este cmd del comentario de @tommcdo, lo tomé inicialmente de DerMike , pero creo que lo tomó simplemente de wikia, así que le di créditos a vim wikia, por lo que no está completamente duplicado ya que la explicación es completamente diferente.
kenorb
Agrega más valor, ya que es una versión mucho más corta con la explicación adecuada y también estoy acreditando las fuentes correctas. Usar comandos de shell es muy simple y conveniente. Si las personas no están de acuerdo, simplemente pueden votar en contra, no es gran cosa.
kenorb
6

En el espíritu de VimL funcional:

:call setline(1, reverse(getline(1, line('$'))))
  • getline(1, line('$'))devuelve una lista de todas las líneas en el búfer. '$'es un argumento especial para el line()que indica la última línea en el búfer.
  • reverse(...)invierte la lista de entrada, en el lugar. reverse(copy(...))Debería usarse si la lista de entrada no se debe modificar.
  • setline(1, ...)reemplaza la línea especificada con el segundo argumento. Cuando el segundo argumento es una lista, el mismo número de líneas que la longitud de la lista se reemplaza con el contenido de la lista.

Si lo desea, también puede definir un comando que tome un rango ( %búfer completo predeterminado )

:command! -bar -range=% Reverse call setline(<line1>, reverse(getline(<line1>, <line2>)))
jamessan
fuente
1
Me gusta esta respuesta Tampoco resalta cosas (si hlsearchestá habilitado) como el :g/comando de las otras respuestas ... ¿Sin embargo, el rendimiento es quizás peor? Como getline(1, line('$'))obtiene todo el búfer en la memoria. reverse()parece estar en su lugar, por lo que debería tener muy poca memoria como tal ...
Martin Tournoij
3

Según la documentación de Vim usr_12.txt - Trucos inteligentes

12.4 Orden de línea inversa

El :globalcomando se puede combinar con el :movecomando para mover todas las líneas antes de la primera línea, lo que da como resultado un archivo invertido. El comando es:

:global/^/m 0

Abreviado:

:g/^/m 0

La ^expresión regular coincide con el comienzo de la línea (incluso si la línea está en blanco). El :movecomando mueve la línea coincidente después de la mítica línea cero, por lo que la línea coincidente actual se convierte en la primera línea del archivo. Como el :globalcomando no se confunde con la numeración de línea cambiante, :globalprocede a hacer coincidir todas las líneas restantes del archivo y coloca cada una como la primera.

Esto también funciona en una variedad de líneas. Primero muévase hacia arriba de la primera línea y márquelo con mt. Luego mueva el cursor a la última línea del rango y escriba:

:'t+1,.g/^/m 't
jecxjo
fuente
1

Usando números relativos. El párrafo comienza en la línea 13 y abarca más 4 líneas.

 :13,13+4g/^/m12
SergioAraujo
fuente