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.txt
cat file1 file2 file2 | sort | uniq --unique
vea mi respuesta a continuación.Respuestas:
Puede lograr esto controlando el formato de las líneas antiguas / nuevas / sin cambios en la
diff
salida 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
diff
opciones que otras soluciones no ofrecen, tales como-i
hacer caso omiso de caso, o varias opciones en blanco (-E
,-b
,-v
etc) por menos estricta coincidencia.Explicación
Las opciones
--new-line-format
,--old-line-format
y--unchanged-line-format
permiten controlar la forma en quediff
los formatos de las diferencias, de forma similar aprintf
los 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
%L
especificador 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
diff
método (junto con otras sugerenciascomm
yjoin
) solo produce la salida esperada con la entrada ordenada , aunque puede usar<(sort ...)
para ordenar en el lugar. Aquí hay unawk
script 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, repitall1
y use elin
operador para determinar si la línea en el archivo1 está presente en el archivo2. (Esto tendrá un resultado diferente para eldiff
mé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
-
significadostdin
en lagawk
línea de comando. Esto lo proporcionasplit
from 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 / BSDsplit
en lugar de una versión de GNU.fuente
diff
: en general, los archivos de entrada serán diferentes,diff
en 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 line
El
man
archivo es realmente bastante legible para esto.fuente
comm
tambié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í sucesivamentecomm
no 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 comandodos2unix
se 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
-F
opció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-F
esto 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
-x
interruptor, 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
-F
esto no escala bien.file2
.-x
opción aparentemente usa más memoria. Confile2
contienen 180M palabras de 6-10 bytes de mi proceso pusoKilled
en 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
,sort
yuniq
:Prueba:
Esto también es relativamente rápido, en comparación con
grep
.fuente
--unique
opción. Debería poder utilizar la opción POSIX estandarizada para esto:| uniq -u
seq 1 1 7
crea números desde 1, con incremento 1, hasta 7, es decir, 1 2 3 4 5 6 7. ¡Y ahí está tu 2!El
-t
se asegura de que compara toda la línea, si tenía un espacio en algunas de las líneas.fuente
comm
,join
requiere 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
combine
demoreutils
paquete, una utilidad que soporta conjuntosnot
,and
,or
,xor
operacioneses 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:
combine
ordena y encuentra líneas únicas en ambos archivos antes de realizar cualquier operación, perodiff
no lo hace. Por lo tanto, puede encontrar diferencias entre la salida dediff
ycombine
.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-lines
bandera, 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.txt
fuente
Encontré que para mí usar una instrucción de bucle if y for normal funcionó perfectamente.
fuente
grep
resultados se expande a varias palabras, o sifile2
el shell puede tratar cualquiera de sus entradas como un globo.