Esiste un modo per "uniq" per colonna?


195

Ho un file .csv come questo:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Devo rimuovere le e-mail duplicate (l'intera riga) dal file (ovvero una delle righe che contengono overflow@example.coml'esempio sopra). Come si usa uniqsolo sul campo 1 (separato da virgole)? Secondo man, uniqnon ha opzioni per le colonne.

Ho provato qualcosa sort | uniqma non funziona.

Risposte:


325
sort -u -t, -k1,1 file
  • -u per unico
  • -t, quindi la virgola è il delimitatore
  • -k1,1 per il campo chiave 1

Risultato del test:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
questo non funziona se la colonna contiene la virgola stessa (con virgolette)
user775187

13
perché hai bisogno di, 1 in -k1,1? perché non solo -k1?
hello_there_andy,

18
@hello_there_andy: questo è spiegato nel manuale ( man sort). Indica la posizione di inizio e fine.
Serrano,

3
@CarlSmotricz: l'ho testato e ha confermato ciò sortche dice la manpage: " -u, --unique con -c, controlla per ordine rigoroso; senza -c, emette solo il primo di una corsa uguale ". Quindi, è davvero "la prima occorrenza del duplicato prima dell'ordinamento".
Geremia,

2
questo cambia anche l'ordine delle linee, no?
rkachach,

102
awk -F"," '!_[$1]++' file
  • -F imposta il separatore di campo.
  • $1 è il primo campo.
  • _[val]cerca valnell'hash_ (una variabile regolare).
  • ++ incrementare e restituire il vecchio valore.
  • ! non restituisce logico.
  • c'è una stampa implicita alla fine.

4
Questo approccio è due volte più veloce
dell'ordinamento

9
Questo ha anche l'ulteriore vantaggio di mantenere le linee nell'ordine originale!
AffluentOwl

8
Se hai bisogno dell'ultimo uniq invece del primo, questo script awk ti aiuterà:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima,

3
@eshwar basta aggiungere altri campi all'indice del dizionario! Ad esempio, !_[$1][$2]++può essere utilizzato per ordinare in base ai primi due campi. Il mio awk-fu non è abbastanza forte da essere in grado di essere unico su una vasta gamma di campi, però. :(
Soham Chowdhury,

1
Brillante! questa opzione è migliore della risposta perché mantiene l'ordine delle righe
rkachach

16

Considerare più colonne.

Ordina e dai un elenco univoco basato sulla colonna 1 e sulla colonna 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : i due punti sono separatori
  • -k 1,1 -k 3,3 basato sulla colonna 1 e colonna 3

8

o se vuoi usare uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

dà:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
Vorrei sottolineare una possibile semplificazione: è possibile scaricare il cat! Invece di eseguire il piping in tr, lascia che tr legga il file usando <. Piping through catè una complicazione inutile comune usata dai principianti. Per grandi quantità di dati c'è un effetto prestazionale da avere.
Carl Smotricz,

4
Buono a sapersi. Grazie! (Naturalmente questo ha senso, pensando a "gatto" e "pigrizia";))
Carsten C.

L'inversione dei campi può essere semplificata con rev.
Hielke Walinga,

5

Se si desidera conservare l'ultimo dei duplicati che è possibile utilizzare

 tac a.csv | sort -u -t, -r -k1,1 |tac

Qual era il mio requisito

Qui

tac inverte il file riga per riga


1

Ecco un modo molto elegante.

Prima formatta il contenuto in modo tale che la colonna da confrontare per unicità sia una larghezza fissa. Un modo per farlo è usare awk printf con un identificatore di larghezza di campo / colonna ("% 15s").

Ora le opzioni -f e -w di uniq possono essere usate per saltare i campi / le colonne precedenti e per specificare la larghezza del confronto (larghezza delle colonne).

Ecco tre esempi.

Nel primo esempio ...

1) Rendi temporaneamente la colonna di interesse una larghezza fissa maggiore o uguale alla larghezza massima del campo.

2) Utilizzare l'opzione -f uniq per saltare le colonne precedenti e utilizzare l'opzione -w uniq per limitare la larghezza a tmp_fixed_width.

3) Rimuovi gli spazi finali dalla colonna per "ripristinarne" la larghezza (supponendo che prima non ci fossero spazi finali).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

Nel secondo esempio ...

Creare una nuova colonna uniq 1. Quindi rimuoverla dopo aver applicato il filtro uniq.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Il terzo esempio è uguale al secondo, ma per più colonne.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

bene, più semplice che isolare la colonna con awk, se hai bisogno di rimuovere tutto con un certo valore per un dato file, perché non fare semplicemente grep -v:

ad es. per eliminare tutto con il valore "col2" nella seconda riga: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Se questo non è abbastanza buono, perché alcune righe potrebbero essere rimosse in modo improprio dal fatto che il valore corrispondente venga mostrato in una colonna diversa, puoi fare qualcosa del genere:

awk per isolare la colonna offensiva: ad es

awk -F, '{print $2 "|" $line}'

-F imposta il campo delimitato da ",", $ 2 indica la colonna 2, seguita da un delimitatore personalizzato e quindi l'intera riga. È quindi possibile filtrare rimuovendo le righe che iniziano con il valore offensivo:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

e quindi rimuovere le cose prima del delimitatore:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(nota: il comando sed è sciatto perché non include valori di escape. Inoltre, il modello sed dovrebbe essere qualcosa di simile a "[^ |] +" (cioè qualsiasi cosa non il delimitatore). Speriamo che sia abbastanza chiaro.


3
Non vuole eliminare le righe, vuole conservare una singola copia di una riga con una stringa specifica. Uniq è il caso d'uso giusto.
ingyhere il

-3

Ordinando prima il file sort, è quindi possibile applicareuniq .

Sembra ordinare il file bene:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Potresti anche fare un po 'di magia AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

Questo non è univoco per colonna, come richiesto nella domanda. Questo è unico per l'intera linea. Inoltre, non è necessario eseguire un ordinamento per eseguire un uniq. I due si escludono a vicenda.
Javid Jamae,

1
Si hai ragione. L'ultimo esempio fa comunque la domanda, anche se la risposta accettata è molto più chiara. Per quanto riguarda sort, quindi uniq, sortdeve essere fatto prima di fare uniqaltrimenti non funziona (ma puoi saltare il secondo comando e semplicemente usare sort -u). Da uniq(1): "Filtra le linee corrispondenti adiacenti da INPUT (o input standard), scrivendo su OUTPUT (o output standard)."
Mikael S,

Ah, hai ragione sull'ordinamento prima di uniq. Non ho mai capito che uniq funziona solo su linee adiacenti. Immagino di usare sempre solo sort -u.
Javid Jamae,
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.