C'è un modo per eliminare le linee duplicate in un file in Unix?
Posso farlo con sort -u
e uniq
comandi, ma voglio usare sed
o awk
. È possibile?
awk
, ma consumerà abbastanza risorse su file più grandi.
C'è un modo per eliminare le linee duplicate in un file in Unix?
Posso farlo con sort -u
e uniq
comandi, ma voglio usare sed
o awk
. È possibile?
awk
, ma consumerà abbastanza risorse su file più grandi.
Risposte:
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 seen
modo che seen[$0] == 1
dopo la prima volta venga trovata una riga e quindi seen[$0] == 2
, e così via.
Awk valuta tutto tranne 0
e ""
(stringa vuota) su true. Se una linea duplicata è posto in seen
poi !seen[$0]
valuterà su false e la linea non verrà scritto l'output.
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
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'
$!
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).
[ -~]
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.
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
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;" .
$ 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:
$!N;
: se la riga corrente NON è l'ultima riga, utilizzare il N
comando per leggere la riga successiva inpattern space
./^(.*)\n\1$/!P
: se il contenuto della corrente pattern space
è duplicate string
separato da due \n
, il che significa che la riga successiva è same
con 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 P
comando per stampare i caratteri in corrente pattern space
util \n
( \n
anche stampato).D
: Usiamo D
comando per eliminare i caratteri a corrente pattern space
util \n
( \n
anche cancellato), allora il contenuto dipattern space
è la riga successiva.D
comando forzerà sed
a saltare al suo FIRST
comando $!N
, ma NON legge la riga successiva dal file o dal flusso di input standard.$ 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:
:loop
comando imposta un label
nome loop
.N
per leggere la riga successiva nel pattern space
.s/^(.*)\n\1$/\1/
per eliminare la riga corrente se la riga successiva è uguale alla riga corrente, utilizziamo il s
comando per eseguire l' delete
azione.s
comando viene eseguito con successo, allora usa la tloop
forza di comando sed
per saltare al label
nome 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 D
comando per delete
la riga che è lo stesso con latest-printed line
, e forzare sed
a saltare al primo comando, che è il p
comando, il contenuto di corrente pattern space
è la nuova riga successiva.busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
Elimina le righe duplicate usando awk.
cat
è inutile. Ad ogni modo, lo uniq
fa già da solo, e non richiede che l'input sia esattamente una parola per riga.
uniq
da solo è sufficiente.