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?
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 -ue uniqcomandi, ma voglio usare sedo 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 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.
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 Ncomando per leggere la riga successiva inpattern space ./^(.*)\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).D: Usiamo Dcomando per eliminare i caratteri a corrente pattern spaceutil \n( \nanche cancellato), allora il contenuto dipattern space è la riga successiva.Dcomando forzerà seda saltare al suo FIRSTcomando $!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:
:loopcomando imposta un labelnome loop.Nper 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 scomando per eseguire l' deleteazione.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.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 uniqfa già da solo, e non richiede che l'input sia esattamente una parola per riga.
uniqda solo è sufficiente.