¿Hay alguna manera de 'uniq' por columna?

195

Tengo un archivo .csv como este:

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
...

Tengo que eliminar correos electrónicos duplicados (toda la línea) del archivo (es decir, una de las líneas que contiene [email protected]el ejemplo anterior). ¿Cómo se usa uniqsolo en el campo 1 (separado por comas)? Según man, uniqno tiene opciones para columnas.

Intenté algo con sort | uniqpero no funciona.

Eno
fuente

Respuestas:

325
sort -u -t, -k1,1 file
  • -u por único
  • -t, entonces la coma es el delimitador
  • -k1,1 para el campo clave 1

Resultado de la prueba:

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 
Carl Smotricz
fuente
3
esto no funciona si la columna contiene una coma en sí (con comillas)
user775187
13
¿por qué necesitas el 1 en -k1,1? ¿Por qué no simplemente -k1?
hello_there_andy
18
@hello_there_andy: Esto se explica en el manual ( man sort). Representa la posición de inicio y parada.
Serrano
3
@CarlSmotricz: Lo probé y confirmó lo que sortdice la página de manual: " -u, --unique con -c, compruebe el orden estricto; sin -c, solo muestra el primero de una ejecución igual ". Por lo tanto, es "la primera aparición del duplicado antes de ordenar".
Geremia
2
esto también cambia el orden de las líneas, ¿no?
rkachach
102
awk -F"," '!_[$1]++' file
  • -F establece el separador de campo.
  • $1 Es el primer campo.
  • _[val]busca valen el hash _(una variable regular).
  • ++ incrementar y devolver el valor anterior.
  • ! devuelve lógico no.
  • Hay una impresión implícita al final.
ghostdog74
fuente
44
Este enfoque es dos veces más rápido que el tipo
bitek
9
¡Esto también tiene el beneficio adicional de mantener las líneas en el orden original!
AffluentOwl
8
Si necesita el último uniq en lugar del primero, este script awk le ayudará:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima
3
¡@eshwar solo agrega más campos al índice del diccionario! Por ejemplo, !_[$1][$2]++se puede usar para ordenar por los dos primeros campos. Sin awkembargo, mi -fu no es lo suficientemente fuerte como para ser único en una variedad de campos. :(
Soham Chowdhury
1
¡Brillante! esta opción es mejor que la respuesta porque mantiene el orden de las líneas
rkachach
16

Para considerar múltiples columnas.

Ordenar y dar una lista única basada en la columna 1 y la columna 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : el colon es separador
  • -k 1,1 -k 3,3 basado en la columna 1 y la columna 3
Prakash
fuente
8

o si quieres usar uniq:

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

da:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1
Carsten C.
fuente
55
Me gustaría señalar una posible simplificación: ¡puedes volcar el cat! En lugar de conectarlo a tr, simplemente deje que tr lea el archivo usando <. La canalización cates una complicación innecesaria común utilizada por los novatos. Para grandes cantidades de datos hay un efecto de rendimiento.
Carl Smotricz
44
Bueno saber. ¡Gracias! (Por supuesto, esto tiene sentido, pensando en "gato" y "pereza";))
Carsten C.
La inversión de campos se puede simplificar con rev.
Hielke Walinga
5

Si desea conservar el último de los duplicados, puede usar

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

Cual era mi requerimiento

aquí

tac revertirá el archivo línea por línea

Sumukh
fuente
1

Aquí hay una manera muy ingeniosa.

Primero formatee el contenido de modo que la columna que se va a comparar para la unicidad sea de ancho fijo. Una forma de hacerlo es utilizar awk printf con un especificador de ancho de campo / columna ("% 15s").

Ahora las opciones -f y -w de uniq se pueden usar para omitir campos / columnas anteriores y para especificar el ancho de comparación (ancho de columna (s)).

Aquí hay tres ejemplos.

En el primer ejemplo ...

1) Haga que la columna de interés tenga un ancho fijo mayor o igual que el ancho máximo del campo.

2) Use la opción -f uniq para omitir las columnas anteriores, y use la opción -w uniq para limitar el ancho a tmp_fixed_width.

3) Elimine los espacios finales de la columna para "restaurar" su ancho (suponiendo que no haya espacios finales de antemano).

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 }'

En el segundo ejemplo ...

Cree una nueva columna uniq 1. Luego quítela después de que se haya aplicado el 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 }'

El tercer ejemplo es el mismo que el segundo, pero para varias columnas.

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 }'
NOYB
fuente
-3

bueno, más simple que aislar la columna con awk, si necesita eliminar todo con un cierto valor para un archivo dado, ¿por qué no simplemente hacer grep -v:

por ejemplo, para eliminar todo con el valor "col2" en la línea del segundo lugar: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Si esto no es lo suficientemente bueno, debido a que algunas líneas pueden ser eliminadas de manera incorrecta al tener el valor correspondiente en una columna diferente, puede hacer algo como esto:

awk para aislar la columna infractora: por ejemplo

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

-F establece el campo delimitado en ",", $ 2 significa columna 2, seguido de un delimitador personalizado y luego toda la línea. Luego puede filtrar eliminando líneas que comienzan con el valor ofensivo:

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

y luego quitar las cosas antes del delimitador:

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

(nota: el comando sed es descuidado porque no incluye valores de escape. Además, el patrón sed debería ser algo así como "[^ |] +" (es decir, cualquier cosa que no sea el delimitador). Pero espero que esto sea lo suficientemente claro.

Steve B.
fuente
3
No quiere purgar líneas, quiere retener una sola copia de una línea con una cadena específica. Uniq es el caso de uso correcto.
ingyhere
-3

Al ordenar el archivo sortprimero, puede aplicar uniq.

Parece ordenar el archivo muy bien:

$ 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

También podrías hacer algo de 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 
Mikael S
fuente
Esto no es único por columna como se solicitó en la pregunta. Esto es único para toda la línea. Además, no tienes que hacer una especie para hacer uniq. Los dos son mutuamente exclusivos.
Javid Jamae
1
Sí, tiene usted razón. Sin embargo, el último ejemplo hace lo que pidió la pregunta, a pesar de que la respuesta aceptada es mucho más clara. En cuanto a sort, entonces uniq, sortdebe hacerse antes de hacerlo, de lo uniqcontrario no funciona (pero puede omitir el segundo comando y simplemente usarlo sort -u). Desde uniq(1): "Filtrar líneas coincidentes adyacentes desde INPUT (o entrada estándar), escribiendo en OUTPUT (o salida estándar)".
Mikael S
Ah, tienes razón en ordenar antes de uniq. Nunca me di cuenta de que uniq solo funciona en líneas adyacentes. Supongo que siempre uso sort -u.
Javid Jamae