¿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?

Scotty Allen
fuente
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
posible duplicado de Cómo invertir líneas de un archivo de texto?
Greg Hewgill
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.

Jason Cohen
fuente
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
Mihai Limbășan
fuente
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 ...

efímero
fuente
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 :-).

Yakir GIladi Edry
fuente
¿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
DerMike
fuente
55
Relacionado: ¿Cómo invertir el orden de las líneas? en Vim SE
kenorb
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
jins
fuente
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.

DigitalRoss
fuente
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 }"
kenorb
fuente
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
konsolebox
fuente
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 
Ekatandilburg
fuente
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
Tim Menzies
fuente
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]))")
Yauhen Yakimovich
fuente
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
lacostenycoder
fuente
2

Esto funcionará tanto en BSD como en GNU.

awk '{arr[i++]=$0} END {while (i>0) print arr[--i] }' filename
R. Kumar
fuente
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.

Mark Booth
fuente
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.

youkaichao
fuente
0

Mejor solución:

tail -n20 file.txt | tac
ITNM
fuente
¡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.

Marius Hofert
fuente
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í.

conductor
fuente
-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

KIC
fuente
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
dosis
fuente
-3
rev
text here

o

rev <file>

o

rev texthere
usuario13575069
fuente
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

Bohdan
fuente
-9
sort -r < filename

o

rev < filename
Yoker Rekoy
fuente
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