Wie kann ich die Reihenfolge der Zeilen in einer Datei umkehren?

641

Ich möchte die Reihenfolge der Zeilen in einer Textdatei (oder stdin) umkehren und den Inhalt jeder Zeile beibehalten.

Also, beginnend mit:

foo
bar
baz

Ich würde gerne mit enden

baz
bar
foo

Gibt es dafür ein Standard-UNIX-Befehlszeilenprogramm?

Scotty Allen
quelle
2
Wichtiger Hinweis zum Umkehren der Zeilen: Stellen Sie sicher, dass Ihre Datei zuerst eine nachgestellte neue Zeile enthält . Andernfalls werden die letzten beiden Zeilen einer Eingabedatei in einer Ausgabedatei zu einer Zeile zusammengeführt (zumindest mit, perl -e 'print reverse <>'aber wahrscheinlich auch für andere Methoden).
Jakub.g
Mögliches Duplikat von Wie werden Zeilen einer Textdatei umgekehrt?
Greg Hewgill
Auch ziemlich fast ein Duplikat (obwohl älter) von unix.stackexchange.com/questions/9356/… . Wie in diesem Fall ist eine Migration zu unix.stackexchange.com wahrscheinlich angemessen.
mc0e

Antworten:

444

BSD Schwanz:

tail -r myfile.txt

Referenz: FreeBSD- , NetBSD- , OpenBSD- und OS X- Handbuchseiten.

Jason Cohen
quelle
120
Denken Sie daran, dass die Option '-r' nicht POSIX-kompatibel ist. Die folgenden sed- und awk-Lösungen funktionieren auch in den besten Systemen.
Waffen
31
Ich habe es gerade unter Ubuntu 12.04 versucht und festgestellt, dass es für meine Version von tail (8.13) keine Option -r gibt. Verwenden Sie stattdessen 'tac' (siehe Mihais Antwort unten).
Odigity
12
Das Häkchen sollte sich nach unten zu tac bewegen. Schwanz -r schlägt auf Ubuntu 12/13, Fedora 20, Suse 11
fehl
2
tail -r ~ / 1 ~ tail: ungültige Option - r Versuchen Sie `tail --help 'für weitere Informationen. sehen aus wie seine neue Option
Bohdan
5
In der Antwort sollte sicherlich erwähnt werden, dass dies nur BSD ist, insbesondere da das OP nach einem "Standard-UNIX" -Dienstprogramm gefragt hat. Dies ist nicht im GNU-Schwanz, also nicht einmal ein De-facto-Standard.
DanC
1394

Erwähnenswert sind auch: tac(the, ahem, reverse of cat). Ein Teil der Coreutils .

Eine Datei in eine andere spiegeln

tac a.txt > b.txt
Mihai Limbășan
quelle
70
Besonders erwähnenswert für diejenigen, die eine Version von tail ohne -r-Option verwenden! (Die meisten Linux-Leute haben GNU-Tail, das kein -r hat, also haben wir GNU-Tac).
Oylenshpeegul
11
Nur eine Anmerkung, weil die Leute tac bereits erwähnt haben, aber tac scheint nicht unter OS X installiert zu sein. Nicht, dass es schwierig wäre, einen Ersatz in Perl zu schreiben, aber ich habe keinen echten.
Chris Lutz
5
Sie können GNU tac für OS X von Fink erhalten. Möglicherweise möchten Sie auch GNU-Schwanz erhalten, da dies einige Dinge tut, die BSD-Schwanz nicht tut.
Oylenshpeegul
25
Wenn Sie OS X mit Homebrew verwenden, können Sie tac mithilfe von brew install coreutilsinstallieren ( gtacwird standardmäßig installiert ).
Robert
3
Eines der Probleme besteht darin, dass die ersten beiden Zeilen als eine Zeile verbunden werden können, wenn die Datei keine nachfolgende neue Zeile enthält. echo -n "abc\ndee" > test; tac test.
CMCDragonkai
161

Es gibt die bekannten Sed-Tricks :

# 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

(Erläuterung: Stellen Sie eine nicht initiale Zeile voran, um den Puffer zu halten, tauschen Sie die Zeile und halten Sie den Puffer, und drucken Sie die Zeile am Ende aus.)

Alternativ (mit schnellerer Ausführung) von den awk-Einzeilern :

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

Wenn Sie sich nicht daran erinnern können,

perl -e 'print reverse <>'

Auf einem System mit GNU-Dienstprogrammen sind die anderen Antworten einfacher, aber nicht die ganze Welt ist GNU / Linux ...

kurzlebig
quelle
4
Aus derselben Quelle: awk '{a [i ++] = $ 0} END {für (j = i-1; j> = 0;) drucke eine [j--]}' Datei * Sowohl die sed- als auch die awk-Version funktionieren mein Busybox-Router. 'tac' und 'tail -r' nicht.
Waffen
8
Ich wünschte, dies wäre die akzeptierte Antwort. coz sed ist immer verfügbar, aber nicht tail -rund tac.
Ryenus
@ryenus: Es tacwird erwartet, dass beliebig große Dateien verarbeitet werden, die nicht in den Speicher passen (die Zeilenlänge ist jedoch immer noch begrenzt). Es ist unklar, ob die sedLösung für solche Dateien funktioniert.
JFS
Einziges
1
Genauer gesagt: Der sed-Code befindet sich in O (n ^ 2) und kann für große Dateien SEHR langsam sein. Daher meine Gegenstimme für die awk-Alternative, linear. Ich habe die Perl-Option nicht ausprobiert, weniger pfeifenfreundlich.
Antoine Lizée
70

Am Ende Ihres Befehls setzen Sie: | tac

tac macht genau das, wonach Sie fragen: "Schreiben Sie jede DATEI in die Standardausgabe, letzte Zeile zuerst."

Tac ist das Gegenteil von Katze :-).

Yakir GIladi Edry
quelle
Warum sollte er? Bitte erläutern Sie den Wert des tacBefehls. Dies ist nützlich für neue Benutzer, die möglicherweise nach demselben Thema suchen.
Nic3500
11
Dies sollte wirklich die akzeptierte Antwort sein. Schade, dass das oben genannte so viele Stimmen hat.
Joelittlejohn
62

Wenn Sie gerade in vimGebrauch sind

:g/^/m0
DerMike
quelle
4
Ich würde das abstimmen, wenn Sie kurz erklären würden, was es getan hat.
mc0e
2
Ja, ich verstehe das, aber ich wollte aufschlüsseln, was die verschiedenen Teile des vim-Befehls tun. Ich habe mir jetzt die Antwort @kenorb verlinkt angesehen, die die Erklärung liefert.
mc0e
5
g bedeutet "mach das global. ^ bedeutet" der Anfang einer Zeile ". m bedeutet" verschiebe die Zeile auf eine neue Zeilennummer. 0 ist die Zeile, in die verschoben werden soll. 0 bedeutet "Anfang der Datei vor der aktuellen Zeile 1". Also: "Finde jede Zeile, die einen Anfang hat, und verschiebe sie in Zeile 0." Sie finden Zeile 1 und verschieben sie nach oben. Tut nichts. Suchen Sie dann Zeile 2 und verschieben Sie sie über Zeile 1 an den Anfang der Datei. Suchen Sie nun Zeile 3 und verschieben Sie sie nach oben. Wiederholen Sie dies für jede Zeile. Am Ende verschieben Sie die letzte Zeile nach oben. Wenn Sie fertig sind, haben Sie alle Zeilen umgekehrt.
Ronopolis
Es ist zu beachten, dass sich der Befehl: g global auf ganz bestimmte Weise verhält und nicht nur Bereiche verwendet. Beispielsweise wird der Befehl ":% m0" die Reihenfolge der Zeilen nicht umkehren, während ":% normal ddggP" (wie auch ": g / ^ / normal ddggP"). Netter Trick und Erklärung ... Oh ja, vergaß das Zeichen "siehe: Hilfe: g für weitere Informationen" ...
Nathan Chappell
51
tac <file_name>

Beispiel:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1
jins
quelle
42
$ (tac 2> /dev/null || tail -r)

Versuchen Sie tac, was unter Linux funktioniert, und wenn das nicht funktioniert, verwenden Sie tail -r, was unter BSD und OSX funktioniert.

DigitalRoss
quelle
4
Warum nicht tac myfile.txt- was fehlt mir?
Salbei
8
@sage, auf das zurückgegriffen werden kann, tail -rfalls taces nicht verfügbar ist. tacist nicht POSIX-konform. Weder ist tail -r. Immer noch nicht narrensicher, aber dies verbessert die Chancen, dass Dinge funktionieren.
Slowpoison
Ich sehe - für Fälle, in denen Sie den Befehl nicht manuell / interaktiv ändern können, wenn er fehlschlägt. Gut genug für mich.
Salbei
3
Sie benötigen einen geeigneten Test, um festzustellen, ob Tac verfügbar ist. Was passiert, wenn taces verfügbar ist, aber nicht genügend RAM zur Verfügung steht und auf halbem Weg einen riesigen Eingabestream verbraucht ? Es schlägt fehl und es tail -rgelingt dann , den Rest des Streams zu verarbeiten, was zu einem falschen Ergebnis führt.
mc0e
@PetrPeller Siehe Antwort oben Kommentar von Robert für OSX verwenden Homebrew. brew install coreutils und verwenden Sie gtacanstelle von tacund wenn Sie es vorziehen, fügen Sie tac als Alias ​​hinzu, gtacwenn Sie beispielsweise ein Shell-Skript
möchten
24

Versuchen Sie den folgenden Befehl:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"
Kenorb
quelle
anstelle der gawk Aussage würde ich so etwas tun: sed 's/^[0-9]*://g'
bng44270
2
warum nicht "nl" anstelle von grep -n verwenden?
Gute Person
3
@GoodPerson kann nlstandardmäßig keine leeren Zeilen nummerieren. Die -baOption ist auf einigen Systemen verfügbar und nicht universell (HP / UX fällt mir ein, obwohl ich es nicht wünschte), während grep -nimmer jede Zeile nummeriert wird , die dem (in diesem Fall leeren) regulären Ausdruck entspricht.
Ghoti
1
Anstelle von gawk benutze ichcut -d: -f2-
Alexander Stumpf
17

Just 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
quelle
2
+1 für die Antwort in Bash und für O (n) und für die
Nichtverwendung der
2
Versuchen Sie dies mit einer Datei, die die Zeile enthält, -neneneneneneneund sehen Sie, warum die Leute empfehlen, immer printf '%s\n'statt zu verwenden echo.
mtraceur
@mtraceur Ich würde diesmal zustimmen, da dies eine allgemeine Funktion ist.
konsolebox
11

Die einfachste Methode ist die Verwendung des tacBefehls. tacist catumgekehrt. Beispiel:

$ 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 
Jekatandilburg
quelle
1
Ich bin mir nicht sicher, warum diese Antwort vor der folgenden angezeigt wird , aber es ist ein Betrug von stackoverflow.com/a/742485/1174784, der Jahre zuvor veröffentlicht wurde.
Anarcat
10

Ich mag die Antwort " tail -r " wirklich , aber meine Lieblingsantwort ist ...

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file
Tim Menzies
quelle
Getestet mit mawkunter Ubuntu 14.04 LTS - funktioniert, ist also nicht GNU awk-spezifisch. +1
Sergiy Kolodyazhnyy
n++kann ersetzt werden durchNR
karakfa
3

BEARBEITEN Folgendes generiert eine zufällig sortierte Liste von Zahlen von 1 bis 10:

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

Dabei werden Punkte durch den tatsächlichen Befehl ersetzt, der die Liste umkehrt

tac

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

Python: Verwenden von [:: - 1] auf 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
quelle
3

Verwenden Sie für Cross-OS-Lösungen (dh OSX, Linux), die möglicherweise tacin einem Shell-Skript verwendet werden, Homebrew, wie andere oben erwähnt haben, und verwenden Sie dann einfach den Alias ​​tac wie folgt:

Installieren Sie lib

Für MacOS

brew install coreutils

Für Linux Debian

sudo apt-get update
sudo apt-get install coreutils 

Fügen Sie dann einen Alias ​​hinzu

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

Dies funktioniert sowohl bei BSD als auch bei GNU.

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

Wenn Sie die vorhandene Datei ändern möchten, können Sie sie ausführen

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

Dadurch entfällt die Notwendigkeit, eine temporäre Datei zu erstellen und dann das Original zu löschen oder umzubenennen. Dies führt zum gleichen Ergebnis. Zum Beispiel:

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

Basierend auf der Antwort von Ephemient , die fast, aber nicht ganz das tat, was ich wollte.

Mark Booth
quelle
1

Es passiert mir, dass ich die letzten nZeilen einer sehr großen Textdatei effizient abrufen möchte .

Das erste, was ich versucht habe, ist tail -n 10000000 file.txt > ans.txt, aber ich fand es sehr langsam, denn ich tailmuss nach dem Ort suchen und gehe dann zurück, um die Ergebnisse zu drucken.

Wenn ich es merke, wechsle ich zu einer anderen Lösung : tac file.txt | head -n 10000000 > ans.txt. Dieses Mal muss sich die Suchposition nur vom Ende zum gewünschten Ort bewegen und das spart 50% Zeit !

Nachricht zum Mitnehmen:

Verwenden tac file.txt | head -n nSie taildiese -rOption, wenn Sie die Option nicht haben .

youkaichao
quelle
0

Beste Lösung:

tail -n20 file.txt | tac
ITNM
quelle
Willkommen bei Stack Overflow! Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen. Bitte versuchen Sie auch, Ihren Code nicht mit erklärenden Kommentaren zu überfüllen. Dies verringert die Lesbarkeit sowohl des Codes als auch der Erklärungen!
Kayess
0

Für Emacs-Benutzer: C-x h(Wählen Sie die gesamte Datei aus) und dann M-x reverse-region. Funktioniert auch nur zum Auswählen von Teilen oder Linien und zum Zurücksetzen dieser.

Marius Hofert
quelle
0

Ich sehe viele interessante Ideen. Aber versuche meine Idee. Geben Sie Ihren Text ein:

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

Dies setzt voraus, dass das Zeichen '~' nicht in der Datei enthalten ist. Dies sollte auf jeder UNIX-Shell funktionieren, die bis 1961 zurückreicht. Oder so ähnlich.

Treiber
quelle
-1

Ich hatte die gleiche Frage, aber ich wollte auch, dass die erste Zeile (Kopfzeile) oben bleibt. Also musste ich die Kraft von awk nutzen

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 funktioniert auch in Cygwin oder Gitbash

KIC
quelle
Das scheint 1\n20\n19...2\neher zu führen als 20\n19...\2\n1\n.
Mark Booth
-1

Sie können es mit vim stdinund tun stdout. Sie können auch verwenden , exum POSIX - kompatibel . vimist nur der visuelle Modus für ex. In der Tat können Sie exmit vim -eoder vim -E(verbesserter exModus) verwenden. vimist nützlich, da im Gegensatz zu ähnlichen Tools seddie Datei zum Bearbeiten gepuffert wird, während sedsie für Streams verwendet wird. Möglicherweise können Sie verwenden awk, aber Sie müssten alles in einer Variablen manuell puffern.

Die Idee ist, Folgendes zu tun:

  1. Lesen Sie von stdin
  2. Verschieben Sie es für jede Zeile in Zeile 1 (umkehren). Befehl ist g/^/m0. Dies bedeutet global für jede Zeile g; Passen Sie den Zeilenanfang an, der mit allem übereinstimmt ^. Verschieben Sie es nach Adresse 0, Zeile 1 m0.
  3. Alles drucken. Befehl ist %p. Dies bedeutet für den Bereich aller Linien %; Drucken Sie die Zeile p.
  4. Beenden Sie gewaltsam, ohne die Datei zu speichern. Befehl ist q!. Dies bedeutet beenden q; kraftvoll !.
# 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

Wie man dies wiederverwendbar macht

Ich benutze ein Skript, das ich aufrufe ved(vim editor like sed), um vim zum Bearbeiten zu verwenden stdin. Fügen Sie dies einer Datei hinzu, die vedin Ihrem Pfad aufgerufen wird:

#!/usr/bin/env sh

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

Ich verwende einen +Befehl anstelle von +'%p' +'q!', weil vim Sie auf 10 Befehle beschränkt. Wenn Sie sie also zusammenführen, können Sie "$@"9 +statt 8 Befehle verwenden.

Dann können Sie tun:

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

Wenn Sie vim 8 nicht haben, geben Sie vedstattdessen Folgendes ein :

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin
dosentmatter
quelle
-2
rev
text here

oder

rev <file>

oder

rev texthere
user13575069
quelle
Hallo, willkommen bei Stack Overflow! Wenn Sie eine Frage beantworten, sollten Sie eine Erklärung hinzufügen, z. B. was der Autor falsch gemacht hat und was Sie getan haben, um sie zu beheben. Ich sage Ihnen dies, weil Ihre Antwort als minderwertig gekennzeichnet wurde und derzeit überprüft wird. Sie können Ihre Antwort bearbeiten , indem Sie auf die Schaltfläche "Bearbeiten" klicken.
Federico Grandi
Esp. Neue Antworten auf alte, gut beantwortete Fragen müssen ausreichend begründet werden, um eine weitere Antwort hinzuzufügen.
Gert Arnold
rev dreht den Text auch horizontal, was nicht das gewünschte Verhalten ist.
D3l_Gato
-4

tail -r funktioniert in den meisten Linux- und MacOS-Systemen

seq 1 20 | Schwanz -r

Bohdan
quelle
-9
sort -r < filename

oder

rev < filename
Yoker Rekoy
quelle
7
sort -rfunktioniert nur, wenn die Eingabe bereits sortiert ist, was hier nicht der Fall ist. revkehrt die Zeichen pro Zeile um, behält aber die Zeilenreihenfolge bei, was auch nicht das ist, wonach Scotty gefragt hat. Diese Antwort ist also überhaupt keine Antwort.
Alexander Stumpf