Puede lograr esto controlando el formato de las líneas antiguas / nuevas / sin cambios en la diff
salida GNU :
diff --new-line-format="" --unchanged-line-format="" file1 file2
Los archivos de entrada deben ordenarse para que esto funcione. Con bash
(y zsh
) puede ordenar en el lugar con la sustitución del proceso <( )
:
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
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 que diff
los formatos de las diferencias, de forma similar a printf
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:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
El %L
especificador es la línea en cuestión y prefijamos cada uno con "+" "-" o "", como diff -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 sugerencias comm
y join
) solo produce la salida esperada con la entrada ordenada , aunque puede usar <(sort ...)
para ordenar en el lugar. Aquí hay un awk
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.
# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
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ínea ss2[]
. Después de leer ambos archivos, repita ll1
y use el in
operador para determinar si la línea en el archivo1 está presente en el archivo2. (Esto tendrá un resultado diferente para el diff
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.
BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
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ínea ss1[]
. Luego, a medida que se lee el archivo2, cada línea coincidente se elimina de ll1[]
y ss1[]
. 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:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Tenga en cuenta el uso y la ubicación del -
significado stdin
en la gawk
línea de comando. Esto lo proporciona split
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 / BSD split
en lugar de una versión de GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt