Come posso invertire l'ordine delle linee in un file?


642

Vorrei invertire l'ordine delle righe in un file di testo (o stdin), preservando il contenuto di ogni riga.

Quindi, cioè iniziando con:

foo
bar
baz

Mi piacerebbe finire con

baz
bar
foo

Esiste un'utilità a riga di comando UNIX standard per questo?


2
Nota importante sull'inversione delle righe: assicurarsi innanzitutto che il file abbia una nuova riga finale . Altrimenti, le ultime due righe di un file di input verranno unite in una riga in un file di output (almeno usando il perl -e 'print reverse <>'ma probabilmente si applica anche ad altri metodi).
jakub.g,


Anche quasi un duplicato (anche se più vecchio) di unix.stackexchange.com/questions/9356/… . Come in quel caso, la migrazione a unix.stackexchange.com è probabilmente appropriata.
mc0e

Risposte:


445

Coda BSD:

tail -r myfile.txt

Riferimento: pagine di manuale di FreeBSD , NetBSD , OpenBSD e OS X.


120
Ricorda solo che l'opzione '-r' non è conforme a POSIX. Le soluzioni sed e awk di seguito funzioneranno anche nei sistemi più pericolosi.
pistole

32
Ho appena provato questo su Ubuntu 12.04 e ho scoperto che non esiste alcuna opzione -r per la mia versione di coda (8.13). Usa invece 'tac' (vedi la risposta di Mihai sotto).
odigity,

12
Il segno di spunta dovrebbe spostarsi sotto a tac. tail -r fallisce su Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa

3
tail -r ~ / 1 ~ tail: opzione non valida - r Prova `tail --help 'per ulteriori informazioni. sembra la sua nuova opzione
Bohdan,

6
La risposta dovrebbe certamente menzionare che si tratta solo di BSD, in particolare dal momento che l'OP ha richiesto un'utilità "standard UNIX". Questo non è nella coda GNU, quindi non è nemmeno uno standard di fatto.
DanC,

1403

Vale anche la pena ricordare: tac(the, ahem, reverse of cat). Parte di coreutils .

Lanciare un file in un altro

tac a.txt > b.txt

72
Particolarmente degno di nota per coloro che usano una versione di coda senza opzione -r! (La maggior parte delle persone Linux ha coda GNU, che non ha -r, quindi abbiamo GNU tac).
Oylenshpeegul,

11
Solo una nota, perché la gente ha menzionato tac prima, ma tac non sembra essere installato su OS X. Non che sarebbe difficile scrivere un sostituto in Perl, ma non ne ho uno vero.
Chris Lutz,

5
Puoi ottenere GNU tac per OS X da Fink. Potresti desiderare di ottenere anche la coda GNU, poiché fa alcune cose che la coda BSD non ha.
oylenshpeegul,

25
Se usi OS X con homebrew, puoi installare tac usando brew install coreutils(installa come gtacpredefinito).
Robert,

3
Uno dei problemi è se il file non ha una nuova riga finale, le prime 2 righe possono essere congiunte come 1 riga. echo -n "abc\ndee" > test; tac test.
CMCDragonkai,

161

Ecco i noti trucchi 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

(Spiegazione: anteporre la riga non iniziale per contenere il buffer, scambiare la riga e conservare il buffer, stampare la riga alla fine)

In alternativa (con esecuzione più veloce) dai awk one-liners :

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

Se non te lo ricordi,

perl -e 'print reverse <>'

Su un sistema con utility GNU, le altre risposte sono più semplici, ma non tutto il mondo è GNU / Linux ...


4
Dalla stessa fonte: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) stampa un file [j--]}' * Entrambe le versioni sed e awk funzionano su il mio router busybox. 'tac' e 'tail -r' no.
pistole

8
Vorrei che questa fosse la risposta accettata. coz sed è sempre disponibile, ma non tail -re tac.
reno il

@ryenus: tacè previsto che gestisca file di grandi dimensioni arbitrari che non rientrano nella memoria (la lunghezza della linea è comunque limitata). Non è chiaro se la sedsoluzione funzioni per tali file.
jfs

Unico problema però: preparati ad aspettare :-)
Antoine Lizée,

1
Più precisamente: il codice sed è in O (n ^ 2) e può essere MOLTO lento per file di grandi dimensioni. Da qui il mio voto per l'alternativa awk, lineare. Non ho provato l'opzione perl, meno adatta alle tubazioni.
Antoine Lizée,

71

alla fine del tuo comando metti: | tac

tac fa esattamente quello che stai chiedendo, "Scrivi ogni FILE sullo standard output, prima l'ultima riga".

tac è l'opposto del gatto :-).


Perché dovrebbe? Spiega il valore del taccomando, questo è utile per i nuovi utenti che potrebbero finire per cercare lo stesso argomento.
Nic3500,

11
Questa dovrebbe davvero essere la risposta accettata. Peccato che quanto sopra abbia così tanti voti.
joelittlejohn,

62

Se ti capita di essere in vimuso

:g/^/m0


4
Vorrei votare questo se mi spiegassi brevemente cosa ha fatto.
mc0e

2
Sì, ho capito, ma volevo dire che cosa stavano facendo i vari bit del comando vim. Ho ora esaminato la risposta @kenorb collegata, che fornisce la spiegazione.
mc0e

5
g significa "fallo a livello globale. ^ significa" l'inizio di una riga ". m significa" sposta la riga su un nuovo numero di riga. 0 è su quale linea spostarsi. 0 significa "inizio del file, prima della riga corrente 1". Quindi: "Trova ogni riga che ha un inizio e spostala sul numero di riga 0." Trovi la linea 1 e la muovi verso l'alto. Non fa nulla. Quindi trova la riga 2 e spostala sopra la riga 1, all'inizio del file. Ora trova la linea 3 e spostarlo è al top. Ripeti questo per ogni riga. Alla fine finisci spostando l'ultima riga verso l'alto. Quando hai finito, hai invertito tutte le linee.
Ronopolis,

Va notato che il comando globale: g si comporta in un modo molto particolare rispetto al semplice utilizzo di intervalli. Ad esempio, il comando ":% m0" non inverte l'ordine delle righe, mentre ":% normal ddggP" lo farà (come farà ": g / ^ / normal ddggP"). Bel trucco e spiegazione ... Oh sì, ho dimenticato il token "vedi: aiuto: g per maggiori informazioni" ...
Nathan Chappell

51
tac <file_name>

esempio:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

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

Prova tac, che funziona su Linux e se non funziona tail -r, funziona su BSD e OSX.


4
Perché no tac myfile.txt- cosa mi sto perdendo?
Salvia,

8
@sage, per tornare indietro tail -rnel caso in cui tacnon sia disponibile. tacnon è conforme a POSIX. Nemmeno lo è tail -r. Ancora non infallibile, ma questo migliora le probabilità di cose che funzionano.
slowpoison,

Vedo - per esempio quando non sei in grado di modificare manualmente / interattivamente il comando quando fallisce. Abbastanza buono per me
salvia

3
È necessario un test adeguato per vedere se tac è disponibile. Cosa succede se tacè disponibile, ma si esaurisce la RAM e si scambia a metà consumo consumando un flusso di input gigante. Non riesce e quindi tail -rriesce a elaborare il resto del flusso fornendo un risultato errato.
mc0e

@PetrPeller Vedi la risposta sopra il commento di Robert per OSX usa l'homebrew. brew install coreutils e usalo gtacal posto di tace se preferisci aggiungi tac come alias gtacse, ad esempio, volessi uno script di shell che lo usasse multipiattaforma (Linux, OSX)
lacostenycoder

24

Prova il seguente comando:

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

invece della dichiarazione gawk, farei una cosa del genere: sed 's/^[0-9]*://g'
bng44270

2
perché non usare "nl" invece di grep -n?
Brava persona il

3
@ GoodPerson, nlper impostazione predefinita non riuscirà a numerare le righe vuote. L' -baopzione è disponibile su alcuni sistemi, non è universale (mi viene in mente HP / UX, anche se vorrei che non lo fosse) mentre grep -nnumererà sempre ogni riga che corrisponde al regex (in questo caso vuoto).
ghoti,

1
Invece di utilizzo gawk Icut -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 per la risposta in bash e per O (n) e per non usare la ricorsione (+3 se potessi)
nhed

2
Prova questo con un file contenente la riga -nenenenenenenee osserva il motivo per cui le persone consigliano di utilizzare sempre printf '%s\n'anziché echo.
mtraceur,

@mtraceur Concordo con questo questa volta poiché questa è una funzione generale.
Konsolebox

11

Il metodo più semplice sta usando il taccomando. tacè catinverso. Esempio:

$ 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
non sono sicuro del motivo per cui questa risposta appare prima di quella qui sotto, ma è una copia di stackoverflow.com/a/742485/1174784 - che è stata pubblicata anni prima.
anarcat,

10

Mi piace molto la risposta " tail -r ", ma la mia risposta gawk preferita è ...

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

Testato con mawkUbuntu 14.04 LTS - funziona, quindi non è specifico per GNU awk. +1
Sergiy Kolodyazhnyy

n++può essere sostituito conNR
karakfa

3

EDIT quanto segue genera un elenco di numeri in ordine casuale da 1 a 10:

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

dove i punti vengono sostituiti con un comando effettivo che inverte l'elenco

tac

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

python: usando [:: - 1] su 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

Per soluzioni cross OS (ovvero OSX, Linux) che possono essere utilizzate tac all'interno di uno script di shell utilizzare homebrew come altri hanno già detto sopra, quindi basta alias tac in questo modo:

Installa lib

Per MacOS

brew install coreutils

Per debian linux

sudo apt-get update
sudo apt-get install coreutils 

Quindi aggiungi alias

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

2

Questo funzionerà sia su BSD che su GNU.

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

1

Se si desidera modificare il file in atto, è possibile eseguire

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

Ciò elimina la necessità di creare un file temporaneo, quindi eliminare o rinominare l'originale e avere lo stesso risultato. Per esempio:

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

Basato sulla risposta di effimero , che ha fatto quasi, ma non del tutto, quello che volevo.


1

Mi capita di voler ottenere in modo efficiente le ultime nrighe di un file di testo molto grande .

La prima cosa che ho provato è tail -n 10000000 file.txt > ans.txt, ma l'ho trovato molto lento, pertail deve cercare la posizione e poi torna indietro per stampare i risultati.

Quando mi rendo conto che, passo ad un'altra soluzione: tac file.txt | head -n 10000000 > ans.txt. Questa volta, la posizione di ricerca deve solo spostarsi dalla fine alla posizione desiderata e consente di risparmiare il 50% di tempo !

Porta a casa il messaggio:

Utilizzare tac file.txt | head -n nse tailnon si dispone -rdell'opzione.


0

Soluzione migliore:

tail -n20 file.txt | tac

Benvenuto in Stack Overflow! Mentre questo frammento di codice può risolvere la domanda, inclusa una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e che queste persone potrebbero non conoscere i motivi del tuo suggerimento sul codice. Cerca anche di non aggiungere il tuo codice a commenti esplicativi, ciò riduce la leggibilità sia del codice che delle spiegazioni!
kayess,

0

Per gli utenti Emacs: C-x h(selezionare l'intero file) e quindi M-x reverse-region. Funziona anche per selezionare solo parti o linee e ripristinarle.


0

Vedo molte idee interessanti. Ma prova la mia idea. Inserisci il tuo testo in questo:

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

il che presuppone che il carattere '~' non sia presente nel file. Questo dovrebbe funzionare su ogni shell UNIX risalente al 1961. O qualcosa del genere.


-1

Avevo la stessa domanda, ma volevo anche che la prima riga (intestazione) rimanesse in cima. Quindi avevo bisogno di usare il potere di 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 funziona anche in cygwin o gitbash


Ciò sembra comportare 1\n20\n19...2\npiuttosto che 20\n19...\2\n1\n.
Mark Booth,

-1

Puoi farlo con vim stdine stdout. È inoltre possibile utilizzare exper essere conformi a POSIX . vimè solo la modalità visiva per ex. In effetti, è possibile utilizzare excon vim -eo vim -E( exmodalità migliorata ). vimè utile perché a differenza di strumenti come sedquesto buffer il file per la modifica, mentre sedviene utilizzato per i flussi. Potresti essere in grado di utilizzare awk, ma dovresti bufferizzare manualmente tutto in una variabile.

L'idea è di fare quanto segue:

  1. Leggi da stdin
  2. Per ogni riga spostalo sulla riga 1 (per invertire). Il comando è g/^/m0. Ciò significa globalmente, per ogni linea g; corrisponde all'inizio della riga, che corrisponde a qualsiasi cosa ^; spostalo dopo l'indirizzo 0, che è la riga 1m0 .
  3. Stampa tutto. Il comando è %p. Questo significa per la gamma di tutte le linee %; stampa la linea p.
  4. Uscita forzata senza salvare il file. Il comando è q!. Questo significa smettere q; con forza !.
# 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

Come renderlo riutilizzabile

Uso uno script che chiamo ved(come vim editor sed) per usare vim per modificarlo stdin. Aggiungi questo a un file chiamato vednel tuo percorso:

#!/usr/bin/env sh

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

Sto usando un +comando invece di +'%p' +'q!', perché vim ti limita a 10 comandi. Quindi unendoli permette "$@"di avere 9+ comandi invece di 8.

Quindi puoi fare:

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

Se non hai vim 8, inseriscilo vedinvece:

#!/usr/bin/env sh

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

-3
rev
text here

o

rev <file>

o

rev texthere

Ciao, benvenuto in Stack Overflow! Quando rispondi a una domanda dovresti includere una sorta di spiegazione, come ciò che l'autore ha fatto di sbagliato e cosa hai fatto per risolverlo. Te lo dico perché la tua risposta è stata contrassegnata come di bassa qualità ed è attualmente in fase di revisione. Puoi modificare la tua risposta facendo clic sul pulsante "Modifica".
Federico Grandi,

Esp. nuove risposte a domande vecchie e ben risposte richiedono un'ampia giustificazione per l'aggiunta di un'altra risposta.
Gert Arnold,

rev capovolge anche il testo in orizzontale, il che non è il comportamento desiderato.
D3l_Gato

-4

tail -r funziona nella maggior parte dei sistemi Linux e MacOS

seq 1 20 | tail -r


-9
sort -r < filename

o

rev < filename

7
sort -rFunziona solo se l'input è già ordinato, il che non è il caso qui. revinverte i caratteri per riga ma mantiene intatto l'ordine delle righe, il che non è ciò che Scotty ha richiesto. Quindi questa risposta in realtà non è affatto una risposta.
Alexander Stumpf,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.