Puede lograr esto controlando el formato de las líneas antiguas / nuevas / sin cambios en la diffsalida 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 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 que difflos formatos de las diferencias, de forma similar a printflos 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 %Lespecificador 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 diffmétodo (junto con otras sugerencias commy join) solo produce la salida esperada con la entrada ordenada , aunque puede usar <(sort ...)para ordenar en el lugar. Aquí hay un awkscript 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 ll1y use el inoperador para determinar si la línea en el archivo1 está presente en el archivo2. (Esto tendrá un resultado diferente para el diffmé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 stdinen la gawklínea de comando. Esto lo proporciona splitfrom 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 spliten lugar de una versión de GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt