¿Cómo eliminar líneas duplicadas en un archivo sin ordenarlo en Unix?

136

¿Hay alguna manera de eliminar líneas duplicadas en un archivo en Unix?

Puedo hacerlo con sort -uy uniqcomandos, pero quiero usar sedo awk. ¿Es eso posible?

Vijay
fuente
11
si te refieres a duplicados consecutivos, uniqsolo es suficiente.
Michael Krelin - hacker
y de lo contrario, creo que es posible con awk, pero consumirá muchos recursos en archivos más grandes.
Michael Krelin - hacker
Los duplicados stackoverflow.com/q/24324350 y stackoverflow.com/q/11532157 tienen respuestas interesantes que idealmente deberían migrarse aquí.
tripleee

Respuestas:

290
awk '!seen[$0]++' file.txt

seenes una matriz asociativa a la que Awk pasará cada línea del archivo. Si una línea no está en la matriz, seen[$0]se evaluará como falsa. El !es un operador lógico NOT e invertirá lo falso a verdadero. Awk imprimirá las líneas donde la expresión se evalúa como verdadera. Los ++incrementos seenpara que seen[$0] == 1después de la primera vez que se encuentre una línea y luego seen[$0] == 2, y así sucesivamente.
Awk evalúa todo menos 0y ""(cadena vacía) a verdadero. Si se coloca una línea duplicada, seenentonces !seen[$0]se evaluará como falsa y la línea no se escribirá en la salida.

Jonas Elfström
fuente
55
Para guardarlo en un archivo podemos hacer estoawk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal
55
Una advertencia importante aquí: si necesita hacer esto para varios archivos, y agrega más archivos al final del comando, o usa un comodín ... la matriz 'visto' se llenará con líneas duplicadas de TODOS los archivos. Si, en cambio, desea tratar cada archivo de forma independiente, deberá hacer algo comofor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9
@ NickK9 que eliminar el duplicado de forma acumulativa en varios archivos es impresionante en sí mismo. Buen consejo
sfscs
31

De http://sed.sourceforge.net/sed1line.txt : (Por favor, no me pregunten cómo funciona esto ;-))

 # 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'
Andre Miller
fuente
geekery ;-) +1, pero el consumo de recursos es inevitable.
Michael Krelin - hacker
3
'$! N; /^(.*)\n\1$/!P; D 'significa "Si no está en la última línea, lea en otra línea. Ahora mire lo que tiene y si NO es algo seguido de una nueva línea y luego lo mismo nuevamente, imprima el material. Ahora elimine las cosas (hasta la nueva línea) ".
Beta
2
'GRAMO; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ n //; h; P 'significa, más o menos, "Añade todo el espacio de espera a esta línea, luego, si ves una línea duplicada, tira todo, de lo contrario, copia todo el desorden en el espacio de espera e imprime la primera parte (que es la línea que acabas de leer "
Beta
¿Es $!necesaria la parte? ¿No sed 'N; /^\(.*\)\n\1$/!P; D'hace lo mismo? No puedo encontrar un ejemplo en el que los dos sean diferentes en mi máquina (luego probé una línea vacía al final con ambas versiones y ambas estaban bien).
eddi
1
Casi 7 años después y nadie respondió @amichair ... <sniff> me pone triste. ;) De todos modos, [ -~]representa un rango de caracteres ASCII de 0x20 (espacio) a 0x7E (tilde). Estos se consideran los caracteres ASCII imprimibles (la página vinculada también tiene 0x7F / eliminar, pero eso no parece correcto). Eso hace que la solución se rompa para cualquiera que no use ASCII o cualquiera que use, digamos, caracteres de tabulación ... El más portátil [^\n]incluye muchos más caracteres ... todos, excepto uno, de hecho.
Capa B
14

Perl one-liner similar a la solución awk de @ jonas:

perl -ne 'print if ! $x{$_}++' file

Esta variación elimina los espacios en blanco finales antes de comparar:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

Esta variación edita el archivo en el lugar:

perl -i -ne 'print if ! $x{$_}++' file

Esta variación edita el archivo en el lugar y realiza una copia de seguridad file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file
Chris Koknat
fuente
6

La línea que Andre Miller publicó anteriormente funciona excepto para las versiones recientes de sed cuando el archivo de entrada termina con una línea en blanco y sin caracteres. En mi Mac, mi CPU simplemente gira.

Bucle infinito si la última línea está en blanco y no tiene caracteres :

sed '$!N; /^\(.*\)\n\1$/!P; D'

No se cuelga, pero pierdes la última línea

sed '$d;N; /^\(.*\)\n\1$/!P; D'

La explicación se encuentra al final de las preguntas frecuentes de sed :

El mantenedor de sed de GNU consideró que, a pesar de los problemas de portabilidad que
esto causaría, cambiar el comando N para imprimir (en lugar de
eliminar) el espacio del patrón era más coherente con las intuiciones de uno
sobre cómo debería comportarse un comando para "agregar la siguiente línea" .
Otro hecho que favoreció el cambio fue que "{N; command;}"
eliminará la última línea si el archivo tiene un número impar de líneas, pero
imprimirá la última línea si el archivo tiene un número par de líneas.

Para convertir los scripts que usaban el comportamiento anterior de N (eliminar
el espacio del patrón al llegar al EOF) a scripts compatibles con
todas las versiones de sed, cambie una "N" solitaria. a "$ d; N;" .

Bradley Kreider
fuente
5

Una forma alternativa de usar Vim (compatible con Vi) :

Eliminar duplicados, líneas consecutivas de un archivo:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

Eliminar líneas duplicadas, no consecutivas y no vacías de un archivo:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

Bohr
fuente
4

La primera solución también es de http://sed.sourceforge.net/sed1line.txt

$ 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

La idea central es:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Explica:

  1. $!N;: si la línea actual NO es la última línea, use el Ncomando para leer la siguiente línea pattern space.
  2. /^(.*)\n\1$/!P: si el contenido de la corriente pattern spaceestá duplicate stringseparado por dos \n, lo que significa que la siguiente línea es samecon la línea actual, NO podemos imprimirlo de acuerdo con nuestra idea central; de lo contrario, lo que significa que la línea actual es la ÚLTIMA aparición de todas sus líneas consecutivas duplicadas, ahora podemos usar el Pcomando para imprimir los caracteres en la pattern spaceutilidad actual \n( \ntambién impresa).
  3. D: utilizamos el Dcomando para eliminar los caracteres en la pattern spaceutilidad actual \n( \ntambién eliminada), luego el contenido depattern space es la siguiente línea.
  4. y el Dcomando obligará seda saltar a su FIRSTcomando $!N, pero NO leerá la siguiente línea del archivo o flujo de entrada estándar.

La segunda solución es fácil de entender (de mí mismo):

$ 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

La idea central es:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Explica:

  1. lea una nueva línea del flujo o archivo de entrada e imprímala una vez.
  2. use el :loopcomando set a labelnamed loop.
  3. use Npara leer la siguiente línea en el pattern space.
  4. use s/^(.*)\n\1$/\1/para eliminar la línea actual si la siguiente línea es la misma que la línea actual, usamos el scomando para realizar la deleteacción.
  5. si el scomando se ejecuta con éxito, utilice la tloopfuerza del comando sedpara saltar al labelnombre loop, que hará el mismo bucle a las siguientes líneas, no hay líneas consecutivas duplicadas de la línea que es latest printed; de lo contrario, use el Dcomando para deletela línea que es la misma que la latest-printed line, y fuerce sedpara saltar al primer comando, que es el pcomando, el contenido de current pattern spacees la siguiente línea nueva.
Weike
fuente
mismo comando en Windows con busybox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
carroñero
-1

Esto se puede lograr usando awk
Abajo de la línea se mostrarán valores únicos

awk file_name | uniq

Puede generar estos valores únicos en un nuevo archivo

awk file_name | uniq > uniq_file_name

nuevo archivo uniq_file_name contendrá solo valores únicos, no duplicados

Aashutosh
fuente
-4
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

Elimina las líneas duplicadas usando awk.

Sadhun
fuente
1
Esto alterará el orden de las líneas.
Vijay
1
¿Qué es un archivo de texto de 20 GB? Demasiado lento.
Alexander Lubyagin el
Como siempre, el cates inútil. De todos modos, uniqya lo hace por sí mismo y no requiere que la entrada sea exactamente una palabra por línea.
tripleee