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ś ją 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.