Tengo dos archivos grandes (conjuntos de nombres de archivo). Aproximadamente 30,000 líneas en cada archivo. Estoy tratando de encontrar una forma rápida de encontrar líneas en el archivo1 que no estén presentes en el archivo2.
Por ejemplo, si este es el archivo1:
line1
line2
line3
Y este es el archivo2:
line1
line4
line5
Entonces mi resultado / salida debería ser:
line2
line3
Esto funciona:
grep -v -f file2 file1
Pero es muy, muy lento cuando se usa en mis archivos grandes.
Sospecho que hay una buena manera de hacer esto usando diff (), pero la salida debería ser solo las líneas, nada más, y parece que no puedo encontrar un interruptor para eso.
¿Alguien puede ayudarme a encontrar una manera rápida de hacer esto, usando bash y binarios básicos de Linux?
EDITAR: Para seguir mi propia pregunta, esta es la mejor manera que he encontrado hasta ahora usando diff ():
diff file2 file1 | grep '^>' | sed 's/^>\ //'
Seguramente, debe haber una mejor manera?

awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txtcat file1 file2 file2 | sort | uniq --uniquevea mi respuesta a continuación.Respuestas:
Puede lograr esto controlando el formato de las líneas antiguas / nuevas / sin cambios en la
diffsalida GNU :Los archivos de entrada deben ordenarse para que esto funcione. Con
bash(yzsh) puede ordenar en el lugar con la sustitución del proceso<( ):En lo anterior, las líneas nuevas y sin cambios se suprimen, por lo que solo se emiten las cambiadas (es decir, las líneas eliminadas en su caso). También puede utilizar un par de
diffopciones que otras soluciones no ofrecen, tales como-ihacer caso omiso de caso, o varias opciones en blanco (-E,-b,-vetc) por menos estricta coincidencia.Explicación
Las opciones
--new-line-format,--old-line-formaty--unchanged-line-formatpermiten controlar la forma en quedifflos formatos de las diferencias, de forma similar aprintflos especificadores de formato. Estas opciones dan formato a líneas nuevas (agregadas), antiguas (eliminadas) y sin cambios respectivamente. Establecer uno para vaciar "" previene la salida de ese tipo de línea.Si está familiarizado con el formato diff unificado , puede recrearlo en parte con:
El
%Lespecificador es la línea en cuestión y prefijamos cada uno con "+" "-" o "", comodiff -u(tenga en cuenta que solo genera diferencias, carece de las líneas---+++y@@en la parte superior de cada cambio agrupado). También puede usar esto para hacer otras cosas útiles como numerar cada línea con%dn.El
diffmétodo (junto con otras sugerenciascommyjoin) solo produce la salida esperada con la entrada ordenada , aunque puede usar<(sort ...)para ordenar en el lugar. Aquí hay unawkscript simple (nawk) (inspirado en los scripts vinculados a la respuesta de Konsolebox) que acepta archivos de entrada ordenados arbitrariamente y genera las líneas que faltan en el orden en que aparecen en el archivo1.Esto almacena todo el contenido del archivo1 línea por línea en una matriz indexada de número de línea
ll1[], y todo el contenido del archivo2 línea por línea en una matriz asociativa indexada de contenido de líneass2[]. Después de leer ambos archivos, repitall1y use elinoperador para determinar si la línea en el archivo1 está presente en el archivo2. (Esto tendrá un resultado diferente para eldiffmétodo si hay duplicados).En el caso de que los archivos sean lo suficientemente grandes como para almacenarlos y causar un problema de memoria, puede cambiar la CPU por memoria almacenando solo el archivo1 y eliminando coincidencias a medida que se lee el archivo2.
Lo anterior almacena todo el contenido del archivo1 en dos matrices, una indexada por número de línea
ll1[]y otra indexada por contenido de líneass1[]. Luego, a medida que se lee el archivo2, cada línea coincidente se elimina dell1[]yss1[]. Al final, salen las líneas restantes del archivo1, conservando el orden original.En este caso, con el problema como se indicó, también puede dividir y conquistar usando GNU
split(el filtrado es una extensión de GNU), ejecuciones repetidas con fragmentos de archivo1 y lectura de archivo2 completamente cada vez:Tenga en cuenta el uso y la ubicación del
-significadostdinen lagawklínea de comando. Esto lo proporcionasplitfrom file1 en fragmentos de 20000 líneas por invocación.Para los usuarios en los sistemas no GNU, es casi seguro que un paquete GNU coreutils puede obtener, incluso en OSX como parte de los de Apple Xcode herramientas que proporciona GNU
diff,awk, aunque sólo un POSIX / BSDspliten lugar de una versión de GNU.fuente
diff: en general, los archivos de entrada serán diferentes,diffen ese caso se devuelve 1 . Considérelo un bono ;-) Si está probando en un script de shell 0 y 1 son códigos de salida esperados, 2 indica un problema.man diff. ¡Gracias!El comando comm (abreviatura de "común") puede ser útil
comm - compare two sorted files line by lineEl
manarchivo es realmente bastante legible para esto.fuente
commtambién tiene una opción para verificar que la entrada esté ordenada--check-order(lo que parece hacer de todos modos, pero esta opción provocará un error en lugar de continuar). Pero para ordenar los archivos, sólo tiene que hacer:com -23 <(sort file1) <(sort file2)y así sucesivamentecommno funcionaba en absoluto. Me tomó un tiempo darme cuenta de que se trata de las terminaciones de línea: incluso las líneas que parecen idénticas se consideran diferentes si tienen diferentes terminaciones de línea. El comandodos2unixse puede usar para convertir las terminaciones de línea CRLF a LF solamente.Como sugirió konsolebox, la solución grep de carteles
en realidad funciona muy bien (rápido) si simplemente agrega la
-Fopción, para tratar los patrones como cadenas fijas en lugar de expresiones regulares. Verifiqué esto en un par de ~ 1000 listas de archivos de líneas que tuve que comparar. Con-Festo tomó 0.031 s (real), mientras que sin tomó 2.278 s (real), al redirigir la salida grep awc -l.Estas pruebas también incluyeron el
-xinterruptor, que son parte necesaria de la solución para garantizar una precisión total en los casos en que el archivo 2 contiene líneas que coinciden con una, o no todas, una o más líneas en el archivo 1.Entonces, una solución que no requiere que se ordenen las entradas, es rápida y flexible (mayúsculas y minúsculas, etc.) es:
Esto no funciona con todas las versiones de grep, por ejemplo, falla en macOS, donde una línea en el archivo 1 se mostrará como no presente en el archivo 2, aunque lo sea, si coincide con otra línea que es una subcadena del mismo . Alternativamente, puede instalar GNU grep en macOS para usar esta solución.
fuente
-Festo no escala bien.file2.-xopción aparentemente usa más memoria. Confile2contienen 180M palabras de 6-10 bytes de mi proceso pusoKilleden una máquina de memoria RAM 32 GB ...¿Cuál es la velocidad de como sort y diff?
fuente
Si estás corto de "herramientas de fantasía", por ejemplo en alguna distribución de Linux mínimo, hay una solución con sólo
cat,sortyuniq:Prueba:
Esto también es relativamente rápido, en comparación con
grep.fuente
--uniqueopción. Debería poder utilizar la opción POSIX estandarizada para esto:| uniq -useq 1 1 7crea números desde 1, con incremento 1, hasta 7, es decir, 1 2 3 4 5 6 7. ¡Y ahí está tu 2!El
-tse asegura de que compara toda la línea, si tenía un espacio en algunas de las líneas.fuente
comm,joinrequiere que ambas líneas de entrada se ordenen en el campo en el que está realizando la operación de combinación.Puedes usar Python:
fuente
Utilizar
combinedemoreutilspaquete, una utilidad que soporta conjuntosnot,and,or,xoroperacioneses decir, dame líneas que estén en el archivo 1 pero no en el archivo 2
O dame líneas en el archivo1 menos líneas en el archivo2
Nota:
combineordena y encuentra líneas únicas en ambos archivos antes de realizar cualquier operación, perodiffno lo hace. Por lo tanto, puede encontrar diferencias entre la salida dediffycombine.Entonces, en efecto, estás diciendo
Encuentra líneas distintas en archivo1 y archivo2 y luego dame líneas en archivo1 menos líneas en archivo2
En mi experiencia, es mucho más rápido que otras opciones
fuente
El uso de fgrep o agregar la opción -F a grep podría ayudar. Pero para cálculos más rápidos, podría usar Awk.
Puede probar uno de estos métodos Awk:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
fuente
La forma en que generalmente hago esto es usando la
--suppress-common-linesbandera, aunque tenga en cuenta que esto solo funciona si lo hace en formato de lado a lado.diff -y --suppress-common-lines file1.txt file2.txtfuente
Encontré que para mí usar una instrucción de bucle if y for normal funcionó perfectamente.
fuente
grepresultados se expande a varias palabras, o sifile2el shell puede tratar cualquiera de sus entradas como un globo.