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
$ 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:
$!N;
: se la riga corrente NON è l’ultima, usa il comando N
per leggere la riga successiva nello pattern space
. /^(.*)\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). 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. D
costringerà sed
a saltare al suo comando FIRST
$!N
, ma NON leggere la riga successiva dal file o dallo stream 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 centrale è:
print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.
spiega:
:loop
comando :loop
imposta label
chiamata loop
. N
per leggere la riga successiva nello pattern space
del pattern space
. 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
. 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.