Come eliminare le righe duplicate in un file senza ordinarlo in Unix?


136

C'è un modo per eliminare le linee duplicate in un file in Unix?

Posso farlo con sort -ue uniqcomandi, ma voglio usare sedo awk. È possibile?


11
se intendi duplicati consecutivi, allora uniqda solo è sufficiente.
Michael Krelin - hacker,

e altrimenti, credo che sia possibile con awk, ma consumerà abbastanza risorse su file più grandi.
Michael Krelin - hacker,

I duplicati stackoverflow.com/q/24324350 e stackoverflow.com/q/11532157 hanno risposte interessanti che dovrebbero idealmente essere migrate qui.
triplo

Risposte:


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

seenè un array associativo a cui Awk passerà ogni riga del file. Se una linea non è nella matrice, seen[$0]verrà valutata come falsa. Il !è un operatore logico NOT e sarà invertire il falso per vero. Awk stamperà le righe in cui l'espressione viene valutata vera. Gli ++incrementi in seenmodo che seen[$0] == 1dopo la prima volta venga trovata una riga e quindi seen[$0] == 2, e così via.
Awk valuta tutto tranne 0e ""(stringa vuota) su true. Se una linea duplicata è posto in seenpoi !seen[$0]valuterà su false e la linea non verrà scritto l'output.


5
Per salvarlo in un file possiamo farloawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal,

5
Un avvertimento importante qui: se è necessario eseguire questa operazione per più file e si incollano più file alla fine del comando o si utilizza un carattere jolly ... l'array "visto" si riempie di righe duplicate da TUTTI i file. Se invece vuoi trattare ogni file in modo indipendente, dovrai fare qualcosa del generefor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9,

@ NickK9 che de-duping cumulativamente su più file è fantastico in sé. Bel suggerimento
sfscs il

31

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'

geekery ;-) +1, ma il consumo di risorse è inevitabile.
Michael Krelin - hacker,

3
! '$ N; /^(.*)\n\1$/!P; D 'significa "Se non sei all'ultima riga, leggi in un'altra riga. Ora guarda cosa hai e se NON È roba seguita da una nuova riga e poi di nuovo la stessa roba, stampa la roba. Ora elimina roba (fino alla nuova riga). "
Beta,

2
'G; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'significa, approssimativamente, "Aggiungi l'intera linea di attesa a questa linea, quindi se vedi una linea duplicata getta via l'intera cosa, altrimenti copia l'intero disordine nello spazio di sospensione e stampa la prima parte (che è la linea che hai appena leggere ".
Beta

La $!parte è necessaria? Non sed 'N; /^\(.*\)\n\1$/!P; D'fa la stessa cosa? Non riesco a trovare un esempio in cui i due siano diversi sulla mia macchina (fwiw ho provato una riga vuota alla fine con entrambe le versioni ed entrambe andavano bene).
eddi

1
Quasi 7 anni dopo e nessuno ha risposto @amichair ... <sniff> mi rende triste. ;) Ad ogni modo, [ -~]rappresenta un intervallo di caratteri ASCII da 0x20 (spazio) a 0x7E (tilde). Questi sono considerati i caratteri ASCII stampabili (la pagina collegata ha anche 0x7F / cancella ma ciò non sembra giusto). Ciò rende la soluzione rotta per chiunque non usi ASCII o chiunque usi, diciamo, i caratteri di tabulazione. Più portatile [^\n]include molti più caratteri ... tutti tranne uno, in effetti.
B Layer

14

Perl one-liner 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 variazione modifica il file sul posto ed esegue un backup file.bak

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

6

Il one-liner che Andre Miller ha pubblicato sopra funziona tranne 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 e basta.

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 sed :

Il manutentore di GNU sed ha ritenuto che, nonostante i problemi di portabilità che
ciò avrebbe causato, cambiando il comando N per stampare (anziché
eliminare) lo spazio del modello era più coerente con le proprie intuizioni
su come dovrebbe comportarsi un comando per "aggiungere la riga successiva" .
Un altro fatto a favore della modifica è che "{N; command;}"
eliminerà 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 righe.

Per convertire gli script che utilizzavano il precedente comportamento di N (eliminando
lo spazio modello al raggiungimento dell'EOF) in script compatibili con
tutte le versioni di sed, cambiare una "N;" a "$ d; N;" .


5

Un modo alternativo usando Vim (compatibile con Vi) :

Elimina le righe consecutive duplicate 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


4

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 principale è:

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 riga, utilizzare il Ncomando per leggere la riga successiva inpattern space .
  2. /^(.*)\n\1$/!P: se il contenuto della corrente pattern spaceè duplicate stringseparato da due \n, il che significa che la riga successiva è samecon la riga corrente, NON possiamo stamparla secondo la nostra idea principale; altrimenti, il che significa riga corrente è la comparsa LAST di tutte le sue duplicati righe consecutive, possiamo usare Pcomando per stampare i caratteri in corrente pattern spaceutil \n( \nanche stampato).
  3. D: Usiamo Dcomando per eliminare i caratteri a corrente pattern spaceutil \n( \nanche cancellato), allora il contenuto dipattern space è la riga successiva.
  4. e il Dcomando forzerà seda saltare al suo FIRSTcomando $!N, ma NON legge la riga successiva dal file o dal flusso 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 principale è:

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 flusso o dal file di input e stamparla una volta.
  2. usa il :loopcomando imposta un labelnome loop.
  3. usare Nper leggere la riga successiva nel pattern space.
  4. utilizzare s/^(.*)\n\1$/\1/per eliminare la riga corrente se la riga successiva è uguale alla riga corrente, utilizziamo il scomando per eseguire l' deleteazione.
  5. se il scomando viene eseguito con successo, allora usa la tloopforza di comando sedper saltare al labelnome loop, che farà lo stesso ciclo per le linee successive util non ci sono linee consecutive duplicate della linea che è latest printed; in caso contrario, utilizzare il Dcomando per deletela riga che è lo stesso con latest-printed line, e forzare seda saltare al primo comando, che è il pcomando, il contenuto di corrente pattern spaceè la nuova riga successiva.

stesso comando su Windows con busybox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
scavenger

-1

Ciò può essere ottenuto utilizzando awk
Below Line per visualizzare valori univoci

awk file_name | uniq

È possibile generare questi valori univoci in un nuovo file

awk file_name | uniq > uniq_file_name

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


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

Elimina le righe duplicate usando awk.


1
Ciò disturberà l'ordine delle linee.
Vijay,

1
Che cos'è circa 20 GB di file di testo? Troppo lento.
Alexander Lubyagin,

Come sempre, l' catè inutile. Ad ogni modo, lo uniqfa già da solo, e non richiede che l'input sia esattamente una parola per riga.
triplo
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.