¿Cómo puedo invertir el orden de las líneas en un archivo?


642

Me gustaría invertir el orden de las líneas en un archivo de texto (o stdin), preservando el contenido de cada línea.

Entonces, es decir, comenzando con:

foo
bar
baz

Me gustaría terminar con

baz
bar
foo

¿Existe una utilidad de línea de comandos UNIX estándar para esto?


2
Nota importante sobre la inversión de las líneas: primero asegúrese de que su archivo tenga una nueva línea final . De lo contrario, las dos últimas líneas de un archivo de entrada se fusionarán en una línea en un archivo de salida (al menos usando el perl -e 'print reverse <>'pero probablemente también se aplica a otros métodos).
jakub.g


También es casi un duplicado (aunque más antiguo) de unix.stackexchange.com/questions/9356/… . Como en ese caso, la migración a unix.stackexchange.com es probablemente apropiada.
mc0e

Respuestas:


445

Cola BSD:

tail -r myfile.txt

Referencia: páginas de manual de FreeBSD , NetBSD , OpenBSD y OS X.


120
Solo recuerde que la opción '-r' no es compatible con POSIX. Las soluciones sed y awk a continuación funcionarán incluso en los sistemas más extravagantes.
armas

32
Acabo de probar esto en Ubuntu 12.04 y descubrí que no hay una opción -r para mi versión de tail (8.13). Use 'tac' en su lugar (vea la respuesta de Mihai a continuación).
Odigity

12
La marca de verificación debe moverse a continuación a tac. tail -r falla en Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa

3
tail -r ~ / 1 ~ tail: opción no válida - r Pruebe `tail --help 'para obtener más información. parece su nueva opción
Bohdan,

66
La respuesta ciertamente debería mencionar que esto es solo BSD, particularmente porque el OP solicitó una utilidad "UNIX estándar". Esto no está en la cola de GNU, por lo que ni siquiera es un estándar de facto.
DanC

1403

También vale la pena mencionar: tac(el, ejem, reverso de cat). Parte de coreutils .

Voltear un archivo a otro

tac a.txt > b.txt

72
¡Especialmente vale la pena mencionar a aquellos que usan una versión de tail sin opción -r! (La mayoría de la gente de Linux tiene GNU tail, que no tiene -r, por lo que tenemos GNU tac).
oylenshpeegul

11
Solo una nota, porque la gente ha mencionado tac antes, pero tac no parece estar instalado en OS X. No es que sea difícil escribir un sustituto en Perl, pero no tengo el real.
Chris Lutz

55
Puede obtener GNU tac para OS X de Fink. Es posible que desee obtener la cola GNU también, ya que hace algunas cosas que la cola BSD no.
oylenshpeegul

25
Si usa OS X con homebrew, puede instalar tac usando brew install coreutils(se instala de manera gtacpredeterminada).
Robert

3
Uno de los problemas es que si el archivo no tiene una nueva línea final, las primeras 2 líneas pueden unirse como 1 línea. echo -n "abc\ndee" > test; tac test.
CMCDragonkai

161

Existen los conocidos trucos sed :

# reverse order of lines (emulates "tac")
# bug/feature in HHsed v1.5 causes blank lines to be deleted
sed '1!G;h;$!d'               # method 1
sed -n '1!G;h;$p'             # method 2

(Explicación: anteponer una línea no inicial para retener el búfer, intercambiar la línea y retener el búfer, imprimir la línea al final)

Alternativamente (con una ejecución más rápida) de los awk one-liners :

awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file*

Si no puedes recordar eso,

perl -e 'print reverse <>'

En un sistema con utilidades GNU, las otras respuestas son más simples, pero no todo el mundo es GNU / Linux ...


44
De la misma fuente: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) imprime un archivo [j--]}' * Las versiones sed y awk funcionan en mi enrutador busybox. 'tac' y 'tail -r' no.
armas de fuego

8
Deseo que esta sea la respuesta aceptada. Coz sed siempre está disponible, pero no tail -ry tac.
ryenus

@ryenus: tacse espera que maneje archivos grandes arbitrarios que no caben en la memoria (aunque la longitud de la línea aún es limitada). No está claro si la sedsolución funciona para dichos archivos.
jfs

Sin embargo, el único problema: prepárate para esperar :-)
Antoine Lizée

1
Más precisamente: el código sed está en O (n ^ 2), y puede ser MUY lento para archivos grandes. De ahí mi voto a favor de la alternativa awk, lineal. No probé la opción perl, menos amigable con las tuberías.
Antoine Lizée

71

al final de su comando ponga: | tac

tac hace exactamente lo que está pidiendo: "Escribir cada ARCHIVO en la salida estándar, la última línea primero".

tac es lo opuesto a cat :-).


¿Por qué debería él? Explique el valor del taccomando, esto es útil para los nuevos usuarios que podrían terminar buscando el mismo tema.
Nic3500

11
Esta realmente debería ser la respuesta aceptada. Es una pena que lo anterior tenga tantos votos.
joelittlejohn

62

Si estás en vimuso

:g/^/m0


44
Lo votaría si explicaras brevemente lo que hizo.
mc0e

2
Sí, entiendo ese bit, pero me refería a desglosar lo que están haciendo los diversos bits del comando vim. Ahora he visto la respuesta @kenorb vinculada, que proporciona la explicación.
mc0e

55
g significa "hacer esto globalmente. ^ significa" el comienzo de una línea ". m significa" mover la línea a un nuevo número de línea. 0 es a qué línea moverse. 0 significa "parte superior del archivo, antes de la línea actual 1". Entonces: "Encuentre cada línea que tenga un comienzo y muévala a la línea número 0". Encuentra la línea 1 y la mueve hacia arriba. No hace nada. Luego busque la línea 2 y muévala por encima de la línea 1, al principio del archivo. Ahora busca la línea 3 y se mueve que a la parte superior. Repita esto para cada línea. Al final terminas moviendo la última línea hacia arriba. Cuando haya terminado, ha invertido todas las líneas.
Ronopolis

Cabe señalar que el comando: g global se comporta de una manera muy particular en comparación con el simple uso de rangos. Por ejemplo, el comando ":% m0" no invertirá el orden de las líneas, mientras que ":% normal ddggP" sí (como lo hará ": g / ^ / normal ddggP"). Buen truco y explicación ... Oh sí, olvidé el token "ver: ayuda: g para más información" ...
Nathan Chappell

51
tac <file_name>

ejemplo:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

42
$ (tac 2> /dev/null || tail -r)

Pruebe tac, que funciona en Linux, y si eso no funciona tail -r, use , que funciona en BSD y OSX.


44
¿Por qué no tac myfile.txt? ¿Qué me estoy perdiendo?
sabio

8
@sage, recurrir a tail -ren caso de tacque no esté disponible. tacno es compatible con POSIX. Tampoco lo es tail -r. Todavía no es infalible, pero esto mejora las probabilidades de que las cosas funcionen.
slowpoison

Ya veo, para los casos en que no puede cambiar el comando de forma manual / interactiva cuando falla. Suficientemente bueno para mi.
sabio

3
Necesita una prueba adecuada para ver si tac está disponible. Lo que sucede si tacestá disponible, pero se queda sin RAM e intercambia a la mitad del consumo de un flujo de entrada gigante. Falla, y luego tail -rlogra procesar el resto de la secuencia dando un resultado incorrecto.
mc0e

@PetrPeller Vea la respuesta anterior comentario de Robert para OSX use homebrew. brew install coreutils y el uso gtacen lugar de tacy si su complemento prefieren tac como un alias que gtacsi por ejemplo quieres un script de shell que lo utilizó multiplataforma (Linux, OSX)
lacostenycoder

24

Pruebe el siguiente comando:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"

lugar de la instrucción gawk, me gustaría hacer algo como esto: sed 's/^[0-9]*://g'
bng44270

2
¿por qué no usar "nl" en lugar de grep -n?
Good Person

3
@GoodPerson, nlpor defecto no podrá numerar líneas vacías. La -baopción está disponible en algunos sistemas, no es universal (HP / UX viene a mi mente, aunque desearía que no lo hiciera), mientras grep -nque siempre numerará cada línea que coincida con la expresión regular (en este caso vacía).
ghoti

1
En lugar de gawk, usocut -d: -f2-
Alexander Stumpf

17

Solo Bash :) (4.0+)

function print_reversed {
    local lines i
    readarray -t lines

    for (( i = ${#lines[@]}; i--; )); do
        printf '%s\n' "${lines[i]}"
    done
}

print_reversed < file

2
+1 por respuesta en bash y por O (n) y por no usar recursividad (+3 si pudiera)
nhed

2
Intente esto con un archivo que contenga la línea -neneneneneneney sea testigo de la razón por la cual la gente recomienda usar siempre en printf '%s\n'lugar de echo.
mtraceur

@mtraceur Estaría de acuerdo con esto esta vez, ya que esta es una función general.
konsolebox

11

El método más simple es usar el taccomando. taces catinverso. Ejemplo:

$ cat order.txt
roger shah 
armin van buuren
fpga vhdl arduino c++ java gridgain
$ tac order.txt > inverted_file.txt
$ cat inverted_file.txt
fpga vhdl arduino c++ java gridgain
armin van buuren
roger shah 

1
No estoy seguro de por qué esta respuesta aparece antes que la siguiente, pero es un engaño de stackoverflow.com/a/742485/1174784 , que se publicó años antes.
anarcat

10

Realmente me gusta la respuesta " tail -r ", pero mi respuesta favorita es ...

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file

Probado con mawkUbuntu 14.04 LTS: funciona, por lo que no es específico de GNU awk. +1
Sergiy Kolodyazhnyy

n++puede ser reemplazado conNR
karakfa

3

EDITAR lo siguiente genera una lista ordenada aleatoriamente de números del 1 al 10:

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') **...**

donde los puntos se reemplazan con un comando real que invierte la lista

tac

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(tac)

python: usando [:: - 1] en sys.stdin

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(python -c "import sys; print(''.join(([line for line in sys.stdin])[::-1]))")

3

Para la solución de sistema operativo cruzado (es decir, OSX, Linux) que puede usar tacdentro de un script de shell, use homebrew como otros han mencionado anteriormente, luego solo alias tac de esta manera:

Instalar lib

Para MacOS

brew install coreutils

Para linux debian

sudo apt-get update
sudo apt-get install coreutils 

Luego agrega alias

echo "alias tac='gtac'" >> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt

2

Esto funcionará tanto en BSD como en GNU.

awk '{arr[i++]=$0} END {while (i>0) print arr[--i] }' filename

1

Si desea modificar el archivo en su lugar, puede ejecutar

sed -i '1!G;h;$!d' filename

Esto elimina la necesidad de crear un archivo temporal y luego eliminar o cambiar el nombre del original y tiene el mismo resultado. Por ejemplo:

$tac file > file2
$sed -i '1!G;h;$!d' file
$diff file file2
$

Basado en la respuesta por ephemient , que hizo casi, pero no del todo, lo que quería.


1

Me sucede que quiero obtener las últimas nlíneas de un archivo de texto muy grande de manera eficiente .

Lo primero que probé es tail -n 10000000 file.txt > ans.txt, pero lo encontré muy lento, ya que tailtiene que buscar la ubicación y luego retroceder para imprimir los resultados.

Cuando me di cuenta, me cambio a otra solución: tac file.txt | head -n 10000000 > ans.txt. ¡Esta vez, la posición de búsqueda solo necesita moverse desde el final hasta la ubicación deseada y ahorra un 50% de tiempo !

Llevar el mensaje a casa:

Úselo tac file.txt | head -n nsi su tailno tiene la -ropción.


0

Mejor solución:

tail -n20 file.txt | tac

¡Bienvenido a Stack Overflow! Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. Por favor, también trate de no saturar su código con comentarios explicativos, ¡esto reduce la legibilidad tanto del código como de las explicaciones!
kayess

0

Para usuarios de Emacs: C-x h(seleccione el archivo completo) y luego M-x reverse-region. También funciona solo para seleccionar partes o líneas y revertirlas.


0

Veo muchas ideas interesantes. Pero prueba mi idea. Canaliza tu texto en esto:

rev | tr '\ n' '~' | rev | tr '~' '\ n'

que supone que el carácter '~' no está en el archivo. Esto debería funcionar en cada shell de UNIX desde 1961. O algo así.


-1

Tenía la misma pregunta, pero también quería que la primera línea (encabezado) permaneciera en la parte superior. Entonces necesitaba usar el poder de awk

cat dax-weekly.csv | awk '1 { last = NR; line[last] = $0; } END { print line[1]; for (i = last; i > 1; i--) { print line[i]; } }'

PS también funciona en cygwin o gitbash


Eso parece dar lugar en 1\n20\n19...2\nlugar de 20\n19...\2\n1\n.
Mark Booth

-1

Puedes hacerlo con vim stdiny stdout. También puede usar expara cumplir con POSIX . vimes solo el modo visual para ex. De hecho, puede usar excon vim -eo vim -E( exmodo mejorado ). vimes útil porque, a diferencia de herramientas como sedesta, almacena el archivo para editarlo, mientras que sedse usa para transmisiones. Es posible que pueda usar awk, pero tendría que almacenar manualmente todo en una variable.

La idea es hacer lo siguiente:

  1. Leer de stdin
  2. Para cada línea muévala a la línea 1 (para invertir). Comando es g/^/m0. Esto significa globalmente, para cada línea g; coincide con el inicio de la línea, que coincide con cualquier cosa ^; muévalo después de la dirección 0, que es la línea 1 m0.
  3. Imprime todo. Comando es %p. Esto significa para el rango de todas las líneas %; imprime la línea p.
  4. Salga con fuerza sin guardar el archivo. Comando es q!. Esto significa dejar de fumar q; enérgicamente !.
# Generate a newline delimited sequence of 1 to 10
$ seq 10
1
2
3
4
5
6
7
8
9
10

# Use - to read from stdin.
# vim has a delay and annoying 'Vim: Reading from stdin...' output
# if you use - to read from stdin. Use --not-a-term to hide output.
# --not-a-term requires vim 8.0.1308 (Nov 2017)
# Use -E for improved ex mode. -e would work here too since I'm not
# using any improved ex mode features.
# each of the commands I explained above are specified with a + sign
# and are run sequentially.
$ seq 10 | vim - --not-a-term -Es +'g/^/m0' +'%p' +'q!'
10
9
8
7
6
5
4
3
2
1
# non improved ex mode works here too, -e.
$ seq 10 | vim - --not-a-term -es +'g/^/m0' +'%p' +'q!'

# If you don't have --not-a-term, use /dev/stdin
seq 10 | vim -E +'g/^/m0' +'%p' +'q!' /dev/stdin

# POSIX compliant (maybe)
# POSIX compliant ex doesn't allow using + sign to specify commands.
# It also might not allow running multiple commands sequentially.
# The docs say "Implementations may support more than a single -c"
# If yours does support multiple -c
$ seq 10 | ex -c "execute -c 'g/^/m0' -c '%p' -c 'q!' /dev/stdin

# If not, you can chain them with the bar, |. This is same as shell
# piping. It's more like shell semi-colon, ;.
# The g command consumes the |, so you can use execute to prevent that.
# Not sure if execute and | is POSIX compliant.
seq 10 | ex -c "execute 'g/^/m0' | %p | q!" /dev/stdin

Cómo hacer que esto sea reutilizable

Uso un script que llamo ved(editor de vim sed) para usar vim para editar stdin. Agregue esto a un archivo llamado veden su ruta:

#!/usr/bin/env sh

vim - --not-a-term -Es "$@" +'%p | q!'

Estoy usando un +comando en lugar de +'%p' +'q!', porque vim te limita a 10 comandos. Por lo tanto, fusionarlos le permite "$@"tener 9 +comandos en lugar de 8.

Entonces puedes hacer:

seq 10 | ved +'g/^/m0'

Si no tienes vim 8, pon esto en su vedlugar:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin

-3
rev
text here

o

rev <file>

o

rev texthere

Hola, bienvenido a Stack Overflow! Cuando responda una pregunta, debe incluir algún tipo de explicación, como qué hizo mal el autor y qué hizo para solucionarlo. Te digo esto porque tu respuesta se ha marcado como de baja calidad y actualmente se está revisando. Puede editar su respuesta haciendo clic en el botón "Editar".
Federico Grandi

Esp. Las nuevas respuestas a preguntas antiguas y bien respondidas necesitan una amplia justificación para agregar otra respuesta.
Gert Arnold

rev también volteará el texto horizontalmente, lo cual no es el comportamiento deseado.
D3l_Gato

-4

tail -r funciona en la mayoría de los sistemas Linux y MacOS

seq 1 20 | cola -r


-9
sort -r < filename

o

rev < filename

77
sort -rsolo funciona si la entrada ya está ordenada, que no es el caso aquí. revinvierte los caracteres por línea pero mantiene intacto el orden de las líneas, que tampoco es lo que Scotty solicitó. Entonces, esta respuesta es en realidad ninguna respuesta.
Alexander Stumpf
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.