¿Cómo eliminar líneas duplicadas dentro de un archivo de texto?

Un archivo de texto enorme (hasta 2 GiB) mío contiene aproximadamente 100 duplicados exactos de cada línea (inútil en mi caso, ya que el archivo es una tabla de datos similar a CSV).

Lo que necesito es eliminar todas las repeticiones mientras (preferiblemente, pero esto se puede sacrificar para un aumento significativo del rendimiento) mantener el orden de secuencia original. En el resultado, cada línea debe ser única. Si hubiera 100 líneas iguales (por lo general, los duplicados están repartidos por el archivo y no serán vecinos), solo quedará una del tipo.

He escrito un programa en Scala (considérelo Java si no conoce Scala) para implementar esto. ¿Pero tal vez hay herramientas nativas escritas en C más rápidas capaces de hacer esto más rápido?

ACTUALIZACIÓN: la solución awk "!seen[$0]++" filename parecía funcionar bien para mí siempre que los archivos estaban cerca de 2 GiB o menos, pero ahora que debo limpiar un archivo de 8 GiB, ya no funciona. Parece infinito en una Mac con 4 GiB de RAM y una PC con Windows 7 de 64 bits con 4 GiB de RAM y el intercambio de 6 GiB simplemente se queda sin memoria. Y no me entusiasma probarlo en Linux con 4 GiB de RAM dada esta experiencia.

Comentarios

  • esto destruirá su pedido, pero, ¿ha probado sort -u? No tengo idea de cómo o si puede ejecutarse en un archivo tan masivo
  • C a menudo no es significativamente más rápido que Java, y si si ‘ lo está ejecutando (en orden) ahora, hay ‘ una posibilidad justa de que ‘ terminará antes de que obtenga una respuesta aquí, impleméntelo y termina de ejecutarse; fuera de servicio, sort -u probablemente será más rápido.

Responder

Una awk solución vista en #bash (Freenode):

awk "!seen[$0]++" filename 

Comentarios

  • Intenté esto en un archivo 2G y me llevó tres minutos en mi cuaderno. Nada mal. También probé uniq filename | awk ‘! visto [$ 0] ++ ‘, pero no era ‘ t más rápido.
  • @HashWizard: este comando no ordena, pero elimina cada aparición siguiente de la misma línea
  • ¿Se pregunta cómo funciona este comando? – Vea aquí: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams sí , funciona si se distribuyen aleatoriamente.
  • preservar las líneas nuevas o las líneas con espacios awk '/^\s*?$/||!seen[$0]++'

Respuesta

Existe un método simple (que no es obvio) que usa utilidades estándar que no requiere una gran memoria excepto para ejecutar sort, que en la mayoría de las implementaciones tiene optimizaciones específicas para archivos grandes (un buen algoritmo de clasificación externo). Una ventaja de este método es que solo recorre todas las líneas dentro de las utilidades de propósito especial, nunca dentro de los idiomas interpretados.

<input nl -b a -s : | # number the lines sort -t : -k 2 -u | # sort and uniquify ignoring the line numbers sort -t : -k 1n | # sort according to the line numbers cut -d : -f 2- >output # remove the line numbers 

Si todas las líneas comienzan con un carácter que no es un espacio en blanco, puede prescindir de algunas de las opciones:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output 

Para una gran cantidad de duplicados, un método que solo requiere almacenar una única copia de cada línea de la memoria funcionará mejor. Con algunos gastos generales de interpretación, hay un script awk muy conciso para eso (ya publicado por enzotib ):

<input awk "!seen[$0]++" 

De manera menos concisa: !seen[$0] {print} {seen[$0] += 1}, es decir, imprima la línea actual si no se ha visto todavía, luego incremente el seen contador para esta línea (las variables no inicializadas o los elementos de la matriz tienen el valor numérico 0).

Para líneas largas, puede ahorrar memoria manteniendo solo una suma de comprobación no falsificable (por ejemplo, un resumen criptográfico) de cada línea . Por ejemplo, al usar SHA-1, solo necesita 20 bytes más una sobrecarga constante por línea. Pero el cálculo de resúmenes es bastante lento; este método solo ganará si tiene una CPU rápida (especialmente una con un acelerador de hardware para calcular los resúmenes) y no mucha memoria en relación con el tamaño del archivo y líneas suficientemente largas. Ninguna utilidad básica le permite calcular una suma de comprobación para cada línea; tendría que soportar la sobrecarga de interpretación de Perl / Python / Ruby /… o escribir un programa compilado dedicado.

<input perl -MDigest::MD5 -ne "$seen{Digest::MD5::md5($_)}++ or print" >output 

Comentarios

  • @Gilles Basado en su explicación de awk '!seen[$0]++', ¿significa que si awk ve 2 líneas duplicadas, mantendrá la siempre primera e ignorará todas ¿Las siguientes? (¿O se quedará con la última?)
  • @ user779159 Conserva la primera: cada línea de entrada se imprime inmediatamente (primera aparición) o no se imprime (repetición).
  • Pero, ¿cómo se compara eso con ordenar -u …?
  • @HashWizard Un sort -u simple cambia el orden.Mi respuesta muestra soluciones que preservan el orden (el orden de las primeras apariciones, para ser precisos).
  • @Gilles diría que es más rápido que ordenar -u para archivos grandes (10G) con 50% de duplicados ?

Responder

sort -u big-csv-file.csv > duplicates-removed.csv 

Tenga en cuenta que el archivo de salida ser ordenados.

Comentarios

  • No tan rápido como el comando awk en otras respuestas, pero conceptualmente simple!
  • @Johann Estoy haciendo esto con bastante frecuencia en archivos con cientos de miles (incluso millones) de cadenas cortas terminadas en una nueva línea. Obtengo los resultados bastante rápido para los experimentos que estoy haciendo. Puede ser más importante si se usa en scripts que se ejecutan una y otra vez, el ahorro de tiempo puede ser considerable.
  • Use sort -u para eliminar duplicados durante la clasificación, en lugar de después. (Y ahorra ancho de banda de memoria) canalizándolo a otro programa). Esto solo es mejor que la versión awk si también desea ordenar su salida. (El OP en esta pregunta quiere que su pedido original se conserve , por lo que esta es una buena respuesta para un caso de uso ligeramente diferente).
  • Me tomó alrededor de un minuto, para mí, para un archivo de 5,5 millones de líneas (1,8 GB en total). Brillante.

Respuesta

Suponiendo que pueda permitirse mantener tanto como el archivo desduplicado en la memoria ( si sus datos están realmente duplicados por un factor de 100, eso debería ser alrededor de 20MiB + de sobrecarga), puede hacerlo muy fácilmente con Perl.

$ perl -ne "print unless $dup{$_}++;" input_file > output_file 

Este también conserva el orden.

Puede extraer el número de ocurrencias de cada línea del hash %dup si así lo desea, como un bono adicional gratuito.

Si prefiere awk, esto también debería hacerlo (la misma lógica que la versión de perl, el mismo orden, los mismos datos recopilados en el dup variable):

$ awk "{if (++dup[$0] == 1) print $0;}" input_file > output_file 

Comentarios

  • Esto es demasiado bueno @Mat, yo estaba a punto de sorber el archivo, lol ;-).
  • Ahora esperando a @ManAtWork para su tejido mágico sed y awk también 🙂
  • impresionante de nuevo para el consejo awk: – )
  • ¿Es posible cambiar el script de Perl para que solo elimine ¿Duplica líneas adyacentes?
  • @dumbledad: uniq hace todo eso por sí mismo

Respuesta

Como ninguna otra respuesta proporcionó soporte en el lugar, aquí hay una:

gawk -i inplace "!a[$0]++" file 

Comentarios

  • ¿Esto preserva el orden? Por cierto, esto no funcionó para mí. Mi versión es: GNU Awk 4.0.2
  • @Leonid sí, lo hace. Imprime la primera aparición de cualquier línea única. El soporte in situ se introdujo por primera vez en la versión 4.1, que se lanzó en 2013.
  • Esta debería ser la respuesta. En realidad, ‘ s elimina la cadena duplicada en el archivo actual o existente, donde la respuesta principal y la mayoría de las respuestas aquí solo imprimen las cadenas uniq / duplicadas y no hacen nada y tenemos que crear otra salida para almacenar el resultado.

Respuesta

Puede usar uniq http://www.computerhope.com/unix/uuniq.htm

uniq informa o filtra líneas repetidas en un archivo.

Comentarios

  • Al dar una respuesta, es preferible dar alguna explicación de POR QUÉ su respuesta es la indicada. Entonces, ¿en qué se diferencia esta respuesta de varias de las respuestas anteriores?
  • De la página de manual de uniq: Nota: 'uniq' does not detect repeated lines unless they are adjacent. Por lo tanto, primero debe ordenarla y soltar el orden de las líneas no duplicadas.

Respuesta

Líneas de Python One:

python -c "import sys; lines = sys.stdin.readlines(); print "".join(sorted(set(lines)))" < InputFile 

Comentarios

  • esto hace que todo el archivo sea absorbido en la memoria y puede no ser una buena opción para el problema del OP ‘ s. Tampoco se garantiza que retenga el orden.
  • Gracias por la sugerencia, ‘ acabo de aprender Python .. acabo de intentar esto con el propósito de aprender .. 🙂
  • Aquí ‘ s una versión de Python 2.7 que no es una sola línea pero (sucintamente) devuelve líneas únicas conservando el orden sin cargar el archivo completo en la memoria o sin crear una sola cadena gigante para alimentar e imprimir
  • Gracias @ 1_CR Tengo algo que aprender hoy 🙂 OrderedDict

Respuesta

Ninguna de las respuestas aquí funcionó para mí en mi Mac, así que escribí un pitón simple guión que funciona para mí. Estoy ignorando los espacios en blanco iniciales / finales y tampoco me importa el consumo de memoria.

import sys inputfile = sys.argv[1] outputfile = sys.argv[2] with open(inputfile) as f: content = f.readlines() content = [x.strip() for x in content] my_list = list(set(content)) with open(outputfile, "w") as output: for item in my_list: output.write("%s\n" % item) 

Guarde lo anterior como único.py y ejecutar así:

python unique.py inputfile.txt outputfile.txt 

Responder

SOLUCIÓN SIN MANTENER EL ORDEN DE SECUENCIA ORIGINAL

Lo hice con el siguiente código.

sort duplicates.txt | uniq > noDuplicates.txt 

El comando sort ordena las líneas alfabéticamente y el comando uniq elimina los duplicados.

NOTA: Por qué ordenamos las líneas primero es que uniq no detecta líneas duplicadas a menos que sean adyacentes.

Comentarios

  • La pregunta solicita un método (preferiblemente ) que mantiene el orden de entrada; ¿Podrías editar tu respuesta para abordar eso? Tenga en cuenta que existen respuestas que usan sort que mantienen el orden de entrada, y una respuesta usando sort sin mantener el orden de entrada pero de una manera más eficiente que canalizar a uniq.
  • @StephenKitt Editado. Inspeccioné otras respuestas, pero no pude ‘ encontrar nada solo con comandos básicos. Gracias por sus comentarios.
  • Le di un enlace a una respuesta con solo comandos básicos, de hecho, solo un comando, sort -u (que es parte de POSIX ) ;-).
  • @StephenKitt Vi esa respuesta. La mía también es una forma de manejar el problema. ¿Qué quieres que haga más? ¿Debo eliminar la respuesta?
  • No, no elimine su respuesta; Solo quería asegurarme de que estaba al tanto de la otra respuesta, dado que dijo que «no podía ‘ encontrar nada solo con comandos básicos».

Respuesta

Con bash 4, una solución bash pura que aprovecha las matrices asociativas se puede utilizar. Aquí hay un ejemplo

unset llist; declare -A llist; while read -r line; do if [[ ${llist[$line]} ]]; then continue else printf "%s\n" "$line" llist[$line]="x" fi done < file.txt 

Comentarios

  • Don ‘ t use read bucles para procesar archivos de texto grandes. bash tiene que leer un byte a la vez para evitar sobrepasar una nueva línea. Bash tampoco es muy rápido en el procesamiento de texto en general en comparación con awk. Si usa esto, read -ra evitará comer barras invertidas en su entrada. Además, no ‘ t olvide unset llist después del bucle, si lo pone en una función de shell o utilícelo de forma interactiva.
  • @PeterCordes, o podría haber hecho referencia a esto 🙂

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *