Jak mogę odwrócić kolejność linii w pliku?


641

Chciałbym odwrócić kolejność linii w pliku tekstowym (lub standardowym), zachowując zawartość każdej linii.

Tak więc, zaczynając od:

foo
bar
baz

Chciałbym z tym skończyć

baz
bar
foo

Czy jest do tego standardowe narzędzie wiersza polecenia UNIX?


2
Ważna uwaga na temat odwracania linii: najpierw upewnij się, że plik ma końcowy znak nowej linii . W przeciwnym razie ostatnie dwa wiersze pliku wejściowego zostaną scalone w jeden wiersz w pliku wyjściowym (przynajmniej przy użyciu, perl -e 'print reverse <>'ale prawdopodobnie dotyczy to również innych metod).
jakub.g


Również prawie duplikat (choć starszy) unix.stackexchange.com/questions/9356/… . Podobnie jak w takim przypadku migracja do unix.stackexchange.com jest prawdopodobnie odpowiednia.
mc0e,

Odpowiedzi:


444

Ogon BSD:

tail -r myfile.txt

Odniesienia: strony podręcznika FreeBSD , NetBSD , OpenBSD i OS X.


120
Pamiętaj tylko, że opcja „-r” nie jest zgodna z POSIX. Poniższe rozwiązania sed i awk będą działały nawet w najśmieszniejszych systemach.
pistolety

31
Właśnie wypróbowałem to na Ubuntu 12.04 i odkryłem, że nie ma opcji -r dla mojej wersji taila (8.13). Zamiast tego użyj „tac” (patrz odpowiedź Mihai poniżej).
odigity

12
Znacznik wyboru powinien przejść poniżej na tac. tail -r zawiedzie na Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa 31.01.14

2
tail -r ~ / 1 ~ tail: nieprawidłowa opcja - r Spróbuj `tail --help ', aby uzyskać więcej informacji. wygląda jak nowa opcja
Bohdan

5
Odpowiedź powinna z pewnością wspomnieć, że jest to tylko BSD, szczególnie, że OP poprosił o „standardowe narzędzie UNIX”. Nie ma go w ogonie GNU, więc nie jest nawet de facto standardem.
DanC

1399

Warto również wspomnieć: tac(ahem, rewers cat). Część coreutils .

Przerzucanie jednego pliku do drugiego

tac a.txt > b.txt

72
Szczególnie warte wspomnienia dla osób używających wersji ogona bez opcji -r! (Większość ludzi z Linuksa ma GNU tail, który nie ma -r, więc mamy GNU tac).
oylenshpeegul

11
Tylko uwaga, ponieważ ludzie wspominali o tac wcześniej, ale tac nie wydaje się być zainstalowany na OS X. Nie dlatego, że trudno byłoby napisać substytut w Perlu, ale ja nie mam prawdziwego.
Chris Lutz

5
Możesz pobrać GNU tac dla OS X z Fink. Możesz także chcieć zdobyć ogon GNU, ponieważ robi on pewne rzeczy, których ogon BSD nie robi.
oylenshpeegul

25
Jeśli używasz OS X z homebrew, możesz zainstalować tac używając brew install coreutils(instaluje gtacsię domyślnie).
Robert

3
Jednym z problemów jest to, że jeśli plik nie ma nowej linii końcowej, pierwsze 2 linie mogą być połączone jako 1 linia. echo -n "abc\ndee" > test; tac test.
CMCDragonkai

161

Istnieją dobrze znane sztuczki 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

(Objaśnienie: poprzedza nieinicjalną linię, aby zatrzymać bufor, zamienić linię i bufor wstrzymania, wydrukować linię na końcu)

Alternatywnie (z szybszym wykonaniem) z jednowarstwowych awk :

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

Jeśli tego nie pamiętasz,

perl -e 'print reverse <>'

W systemie z narzędziami GNU pozostałe odpowiedzi są prostsze, ale nie cały świat to GNU / Linux ...


4
Z tego samego źródła: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) wydrukuj plik [j--]}' * Obie wersje sed i awk działają na mój router busybox. „tac” i „tail -r” nie.
pistolety

8
Chciałbym, żeby to była zaakceptowana odpowiedź. bo sed jest zawsze dostępny, ale nie tail -ri tac.
ryenus

@ryenus: tacoczekuje się, że obsłuży dowolne duże pliki, które nie mieszczą się w pamięci (długość linii jest wciąż ograniczona). Nie jest jasne, czy sedrozwiązanie działa dla takich plików.
jfs

Jedyny problem: bądź przygotowany na czekanie :-)
Antoine Lizée

1
Dokładniej: kod sed znajduje się w O (n ^ 2) i może być BARDZO powolny w przypadku dużych plików. Stąd moja opinia o alternatywie awk, liniowej. Nie wypróbowałem opcji perl, mniej przyjaznej dla pipingu.
Antoine Lizée

70

na końcu polecenia umieść: | tac

tac robi dokładnie to, o co prosisz, „zapisuje każdy PLIK na standardowe wyjście, najpierw w ostatniej linii”.

tac jest przeciwieństwem cat :-).


Dlaczego miałby to robić? Proszę wyjaśnić wartość tacpolecenia, jest to przydatne dla nowych użytkowników, którzy mogą skończyć z wyszukiwaniem tego samego tematu.
Nic3500,

11
To naprawdę powinna być zaakceptowana odpowiedź. Szkoda, że ​​powyższe ma tak wiele głosów.
joelittlejohn

62

Jeśli zdarzy ci się być w vimużyciu

:g/^/m0


4
Głosowałbym za tym, gdybyś krótko wyjaśnił, co to zrobił.
mc0e,

2
Tak, rozumiem to, ale miałem na myśli rozbicie tego, co robią różne bity polecenia vim. Spojrzałem teraz na odpowiedź @kenorb, która zawiera wyjaśnienie.
mc0e,

5
g oznacza „zrób to globalnie. ^ oznacza„ początek linii ”. m oznacza„ przenieś linię na nowy numer linii. 0 to linia, do której należy przejść. 0 oznacza „początek pliku, przed bieżącą linią 1”. Więc: „Znajdź każdą linię, która ma początek i przenieś ją do linii nr 0.” Znajdujesz linię 1 i przenosisz ją na górę. Nic nie robi. Następnie znajdź wiersz 2 i przenieś go powyżej wiersza 1 na górę pliku. Teraz znajdź linię 3 i przenieś na górę. Powtórz to dla każdej linii. Na koniec kończysz, przesuwając ostatnią linię na górę. Kiedy skończysz, odwróciłeś wszystkie linie.
Ronopolis,

Należy zauważyć, że globalne polecenie: g zachowuje się w bardzo szczególny sposób w porównaniu do zwykłego używania zakresów. Na przykład polecenie „:% m0” nie odwróci kolejności wierszy, a „:% normal ddggP” (podobnie jak „: g / ^ / normal ddggP”). Fajna sztuczka i wyjaśnienie ... O tak, zapomniałem tokena „patrz: pomoc: g, aby uzyskać więcej informacji” ...
Nathan Chappell

51
tac <file_name>

przykład:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

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

Spróbuj tac, który działa na Linuksie, a jeśli to nie działa użyj tail -r, który działa na BSD i OSX.


4
Dlaczego nie tac myfile.txt- czego mi brakuje?
mędrzec

8
@sage, do którego można wrócić tail -rw przypadku, gdy tacnie jest dostępny. tacnie jest zgodny z POSIX. Ani też nie tail -r. Nadal nie jest niezawodny, ale poprawia to szanse działania.
slowpoison

Rozumiem - w przypadkach, gdy nie można ręcznie / interaktywnie zmienić polecenia, gdy się nie powiedzie. Wystarczająco dobrze dla mnie.
mędrzec

3
Potrzebujesz odpowiedniego testu, aby sprawdzić, czy tac jest dostępny. Co się stanie, jeśli tacjest dostępne, ale zabraknie pamięci RAM i zostanie zamienione w połowie korzystania z gigantycznego strumienia wejściowego. Nie udaje się, a następnie tail -rudaje się przetworzyć pozostałą część strumienia, dając niepoprawny wynik.
mc0e,

@PetrPeller Zobacz odpowiedź powyżej komentarz Roberta dla OSX używać homebrew. brew install coreutils i użyj gtaczamiast taci jeśli wolisz, dodaj tac jako alias, gtacjeśli na przykład chcesz skrypt powłoki, który używał go na różnych platformach (Linux, OSX)
lacostenycoder

24

Spróbuj wykonać następujące polecenie:

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

zamiast oświadczenia gawk zrobiłbym coś takiego: sed 's/^[0-9]*://g'
bng44270

2
dlaczego nie użyć „nl” zamiast grep -n?
Dobry człowiek,

3
@ GoodPerson, nldomyślnie nie będzie numerować pustych linii. Ta -baopcja jest dostępna w niektórych systemach, nie jest uniwersalna (HP / UX przychodzi na myśl, choć wolałbym, żeby nie była), podczas gdy grep -nzawsze będzie numerować każdą linię pasującą do (w tym przypadku pustego) wyrażenia regularnego.
ghoti

1
Zamiast gawk używamcut -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

2
+1 za odpowiedź w bashu i za O (n) i za niestosowanie rekurencji (+3, gdybym mógł)
nhed

2
Wypróbuj to z plikiem zawierającym wiersz -nenenenenenenei zobacz, dlaczego ludzie zalecają zawsze używać printf '%s\n'zamiast echo.
mtraceur

@mtraceur Zgodziłbym się tym razem, ponieważ jest to funkcja ogólna.
konsolebox

11

Najprostszą metodą jest użycie tacpolecenia. tacjest catodwrotna. Przykład:

$ 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
nie jestem pewien, dlaczego ta odpowiedź pojawia się przed poniższą, ale jest to duplikat stackoverflow.com/a/742485/1174784 - który został opublikowany wiele lat wcześniej.
anarcat

10

Naprawdę podoba mi się odpowiedź „ tail -r ”, ale moja ulubiona odpowiedź gawk to…

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

Testowane mawkna Ubuntu 14.04 LTS - działa, więc nie jest specyficzne dla GNU awk. +1
Sergiy Kolodyazhnyy 13.04.16

n++można zastąpićNR
karakfa

3

EDYCJA następujące generuje losowo posortowaną listę liczb od 1 do 10:

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

gdzie kropki są zastępowane rzeczywistym poleceniem, które odwraca listę

tac

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

python: używając [:: - 1] na 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

Dla różnych systemów operacyjnych (tj. OSX, Linux), które mogą być użyte tac w skrypcie powłoki, użyj homebrew, jak inni wspominali powyżej, a następnie alias tac:

Zainstaluj lib

W systemie MacOS

brew install coreutils

Dla systemu Linux Debian

sudo apt-get update
sudo apt-get install coreutils 

Następnie dodaj alias

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

2

Będzie to działać zarówno na BSD, jak i GNU.

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

1

Jeśli chcesz zmodyfikować plik na swoim miejscu, możesz uruchomić

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

Eliminuje to potrzebę utworzenia pliku tymczasowego, a następnie usunięcia lub zmiany nazwy oryginału i daje ten sam rezultat. Na przykład:

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

Na podstawie odpowiedzi udzielonej przez ephemient , która zrobiła prawie, ale nie całkiem, to, czego chciałem.


1

Zdarza mi się, że chcę sprawnie uzyskać ostatnie nwiersze bardzo dużego pliku tekstowego .

Pierwszą rzeczą, której próbowałem tail -n 10000000 file.txt > ans.txt, było bardzo wolne, ponieważ muszę tailszukać lokalizacji, a następnie wraca, aby wydrukować wyniki.

Kiedy zdaje sobie sprawy, mogę przełączyć na inne rozwiązanie: tac file.txt | head -n 10000000 > ans.txt. Tym razem pozycja poszukiwania musi tylko przejść od końca do pożądanej lokalizacji i oszczędza 50% czasu !

Wiadomość do domu:

Użyj, tac file.txt | head -n njeśli tailnie masz takiej -ropcji.


0

Najlepsze rozwiązanie:

tail -n20 file.txt | tac

Witamy w Stack Overflow! Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu. Staraj się również nie tłoczyć kodu objaśniającymi komentarzami, co zmniejsza czytelność zarówno kodu, jak i objaśnień!
kayess

0

Dla użytkowników Emacsa: C-x h(wybierz cały plik), a następnie M-x reverse-region. Działa również do zaznaczania części lub linii i przywracania ich.


0

Widzę wiele interesujących pomysłów. Ale wypróbuj mój pomysł. Dodaj do tego swój tekst:

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

który zakłada, że ​​znaku „~” nie ma w pliku. Powinno to działać na każdej powłoce UNIX od 1961 roku. Lub coś w tym rodzaju.


-1

Miałem to samo pytanie, ale chciałem też, aby pierwsza linia (nagłówek) pozostała na górze. Musiałem więc użyć mocy 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 działa również w cygwin lub gitbash


Wydaje się, że prowadzi to 1\n20\n19...2\nraczej do niż 20\n19...\2\n1\n.
Mark Booth,

-1

Możesz to zrobić za pomocą vim stdini stdout. Możesz także użyć, exaby zachować zgodność z POSIX . vimjest tylko trybem wizualnym ex. W rzeczywistości możesz używać exz vim -elub vim -E( extryb ulepszony ). vimjest przydatny, ponieważ w przeciwieństwie do takich narzędzi sedbuforuje plik do edycji, podczas gdy sedjest używany do strumieni. Możesz być w stanie użyć awk, ale musisz ręcznie buforować wszystko w zmiennej.

Chodzi o to, aby wykonać następujące czynności:

  1. Czytaj ze standardowego
  2. Dla każdej linii przenieś ją do linii 1 (do tyłu). Dowództwo jest g/^/m0. Oznacza to globalnie, dla każdej linii g; dopasuj początek linii, który pasuje do wszystkiego ^; przenieś go po adresie 0, czyli linii 1m0 .
  3. Wydrukuj wszystko. Dowództwo jest %p. Oznacza to zakres wszystkich linii %; wydrukuj linię p.
  4. Wymuś zamknięcie bez zapisywania pliku. Dowództwo jest q!. To oznacza wyjście q; zdecydowanie !.
# 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

Jak uczynić to wielokrotnego użytku

Używam skryptu, który vedwywołuję (jak edytor vim sed), aby używać vima do edycji stdin. Dodaj to do pliku o nazwie vedna swojej ścieżce:

#!/usr/bin/env sh

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

Używam jednego +polecenia zamiast +'%p' +'q!', ponieważ vim ogranicza cię do 10 poleceń. Tak więc ich połączenie pozwala "$@"mieć 9+ poleceń zamiast 8.

Następnie możesz zrobić:

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

Jeśli nie masz vima 8, wstaw to w vedzamian:

#!/usr/bin/env sh

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

-3
rev
text here

lub

rev <file>

lub

rev texthere

Cześć, witamy w Stack Overflow! Kiedy odpowiadasz na pytanie, powinieneś podać jakieś wyjaśnienie, na przykład co zrobił autor źle i co zrobiłeś, aby to naprawić. Mówię ci to, ponieważ Twoja odpowiedź została oznaczona jako niskiej jakości i jest obecnie sprawdzana. Możesz edytować swoją odpowiedź, klikając przycisk „Edytuj”.
Federico Grandi

Esp. nowe odpowiedzi na stare, dobrze zadane pytania wymagają obszernego uzasadnienia dodania kolejnej odpowiedzi.
Gert Arnold

rev obróci tekst również w poziomie, co nie jest pożądanym zachowaniem.
D3l_Gato

-4

ogon -r działa w większości systemów Linux i MacOS

seq 1 20 | ogon -r


-9
sort -r < filename

lub

rev < filename

7
sort -rdziała tylko wtedy, gdy dane wejściowe są już posortowane, co nie ma miejsca w tym przypadku. revodwraca znaki w wierszu, ale zachowuje nienaruszoną kolejność wierszy, co również nie jest tym, o co poprosił Scotty. Tak więc ta odpowiedź nie jest wcale odpowiedzią.
Alexander Stumpf,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.