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?

Scotty Allen
źródło
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
możliwy duplikat Jak odwrócić linie pliku tekstowego?
Greg Hewgill
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.

Jason Cohen
źródło
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
Mihai Limbășan
źródło
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 ...

efemeryczny
źródło
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 :-).

Yakir GIladi Edry
źródło
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
DerMike
źródło
5
Powiązane: Jak odwrócić kolejność linii? w Vim SE
kenorb
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
jins
źródło
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.

DigitalRoss
źródło
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 }"
kenorb
źródło
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
konsolebox
źródło
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 
Jekatandilburg
źródło
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
Tim Menzies
źródło
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]))")
Yauhen Yakimovich
źródło
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
lacostenycoder
źródło
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
R. Kumar
źródło
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.

Mark Booth
źródło
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.

youkaichao
źródło
0

Najlepsze rozwiązanie:

tail -n20 file.txt | tac
ITNM
źródło
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.

Marius Hofert
źródło
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.

kierowca
źródło
-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

WWI
źródło
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
dosentmatter
źródło
-3
rev
text here

lub

rev <file>

lub

rev texthere
użytkownik13575069
źródło
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

Bohdan
źródło
-9
sort -r < filename

lub

rev < filename
Yoker Rekoy
źródło
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,