Come posso eliminare le righe duplicate in un file in Unix?

C’è un modo per eliminare le righe duplicate in un file in Unix?

Posso farlo con i comandi sort -u e uniq , ma voglio usare sed o awk . È ansible?

 awk '!seen[$0]++' file.txt 

seen è un array associativo che Awk passerà a ogni riga del file. Se una linea non è nell’array, allora seen[$0] verrà valutata come falsa. Il ! è un operatore logico NOT e invertirà il falso in vero. Awk stamperà le linee in cui l’espressione viene valutata come vera. Gli incrementi ++ seen modo tale da seen[$0] == 1 dopo la prima volta che viene trovata una linea e poi seen[$0] == 2 , e così via.
Awk valuta tutto tranne 0 e "" (stringa vuota) su true. Se una linea duplicata viene posizionata in seen allora !seen[$0] verrà valutata come falsa e la riga non verrà scritta nell’output.

Da http://sed.sourceforge.net/sed1line.txt : (per favore non chiedermi come funziona ;-))

  # delete duplicate, consecutive lines from a file (emulates "uniq"). # First line in a set of duplicate lines is kept, rest are deleted. sed '$!N; /^\(.*\)\n\1$/!P; D' # delete duplicate, nonconsecutive lines from a file. Beware not to # overflow the buffer size of the hold space, or else use GNU sed. sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' 

One-liner Perl simile alla soluzione awk di @ jonas:

 perl -ne 'print if ! $x{$_}++' file 

Questa variazione rimuove gli spazi bianchi finali prima di confrontare:

 perl -lne 's/\s*$//; print if ! $x{$_}++' file 

Questa variazione modifica il file sul posto:

 perl -i -ne 'print if ! $x{$_}++' file 

Questa variante modifica il file sul posto e crea un file di file.bak

 perl -i.bak -ne 'print if ! $x{$_}++' file 

L’one-liner che Andre Miller ha pubblicato sopra funziona solo per le versioni recenti di sed quando il file di input termina con una riga vuota e senza caratteri. Sul mio Mac la mia CPU gira appena.

Ciclo infinito se l’ultima riga è vuota e non ha caratteri :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Non si blocca, ma si perde l’ultima linea

sed '$d;N; /^\(.*\)\n\1$/!P; D'

La spiegazione è alla fine delle FAQ di sed :

Il manutentore di GNU sentiva che, nonostante i problemi di portabilità
ciò causerebbe, cambiando il comando N per stampare (piuttosto che
cancella) lo spazio del pattern era più coerente con le proprie intuizioni
su come dovrebbe comportarsi un comando per “aggiungere la riga successiva”.
Un altro fatto che favorisce il cambiamento è che “{N; command;}” sarà
cancella l’ultima riga se il file ha un numero dispari di righe, ma
stampa l’ultima riga se il file ha un numero pari di linee.

Per convertire script che hanno utilizzato il precedente comportamento di N (eliminazione
lo spazio del pattern al raggiungimento dell’EOF) agli script compatibili con
tutte le versioni di sed, cambia un solitario “N;” a “$ d; N;” .

Un modo alternativo usando Vim (Vi compatibile) :

Elimina duplicati, righe consecutive da un file:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Elimina le righe duplicate, non consecutive e non vuote da un file:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

La prima soluzione è anche da http://sed.sourceforge.net/sed1line.txt

 $ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D' 1 2 3 4 5 

l’idea centrale è:

 print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP. 

spiega:

  1. $!N; : se la riga corrente NON è l’ultima, usa il comando N per leggere la riga successiva nello pattern space .
  2. /^(.*)\n\1$/!P : se il contenuto dello pattern space attuale è costituito da due duplicate string separate separate da \n , il che significa che la riga successiva è la same riga corrente, NON possiamo stamparla secondo la nostra idea principale; altrimenti, il che significa che la riga corrente è l’ULTIMO aspetto di tutte le sue doppie righe consecutive, ora possiamo usare il comando P per stampare i caratteri nello pattern space attuale \n ( \n anche stampato).
  3. D : usiamo il comando D per cancellare i caratteri nello pattern space attuale util \n ( \n anche cancellato), quindi il contenuto dello pattern space è la riga successiva.
  4. e il comando D costringerà sed a saltare al suo comando FIRST $!N , ma NON leggere la riga successiva dal file o dallo stream di input standard.

La seconda soluzione è facile da capire (da me stesso):

 $ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D' 1 2 3 4 5 

l’idea centrale è:

 print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP. 

spiega:

  1. leggere una nuova riga dal stream o dal file di input e stamparla una volta.
  2. usa :loop comando :loop imposta label chiamata loop .
  3. usa N per leggere la riga successiva nello pattern space del pattern space .
  4. usa s/^(.*)\n\1$/\1/ per cancellare la riga corrente se la riga successiva è uguale alla riga corrente, usiamo s comando s per fare l’azione di delete .
  5. se il comando s viene eseguito con successo, utilizzare la forza di comando di tloop sed per passare label denominata loop , che eseguirà lo stesso ciclo sulle righe successive, non ci sono linee duplicate consecutive della linea che è stata latest printed ; altrimenti, usare il comando D per delete la linea che è la stessa della latest-printed line e forzare sed per passare al primo comando, che è il comando p , il contenuto dello pattern space attuale è la nuova riga successiva.

Questo può essere ottenuto usando awk
Sotto la riga verranno visualizzati i valori unici

 awk file_name | uniq 

È ansible emettere questi valori univoci in un nuovo file

 awk file_name | uniq > uniq_file_name 

il nuovo file uniq_file_name conterrà solo valori Unici, nessun duplicato

 cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}' 

Elimina le linee duplicate usando awk.