Compare dos archivos línea por línea y genere la diferencia en otro archivo

121

Quiero comparar file1 con file2 y generar un file3 que contiene las líneas en file1 que no están presentes en file2.

Balualways
fuente
Probé con diff pero genera algunos números y otros símbolos delante de diferentes líneas que me dificulta comparar archivos.
dom

Respuestas:

216

diff (1) no es la respuesta, pero comm (1) sí.

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

Entonces

comm -2 -3 file1 file2 > file3

Los archivos de entrada deben estar ordenados. Si no es así, ordénelos primero. Esto se puede hacer con un archivo temporal o ...

comm -2 -3 <(sort file1) <(sort file2) > file3

siempre que su shell admita la sustitución de procesos (bash lo hace).

sorigal
fuente
1
Recuerde que dos archivos deben estar ordenados y son únicos
andy
6
Puede agrupar las opciones:comm -23
Paolo M
¿Qué significa "ordenado"? ¿Que las líneas tienen el mismo orden? Entonces probablemente esté bien para la mayoría de los casos de uso, como por ejemplo, verificar qué líneas se han agregado comparándolas con una versión anterior respaldada. Si las líneas recién agregadas no pueden estar entre las líneas existentes, eso es un problema mayor.
Egor Hans
@EgorHans: si el archivo tiene, por ejemplo, líneas que contienen números enteros como "3 \ n1 \ n3 \ n2 \ n", las líneas deben reordenarse primero en orden ascendente o descendente, por ejemplo, "\ 1 \ n2 \ n3 \ n3 \ n" con duplicados adyacente. Eso está "ordenado" y ambos archivos se deben ordenar de manera similar. Cuando el archivo más nuevo tiene nuevas líneas, no importa si están "entre líneas existentes" porque después de la clasificación no lo están, están en orden.
Sorigal
48

La utilidad Unix diffestá diseñada exactamente para este propósito.

$ diff -u file1 file2 > file3

Consulte el manual e Internet para conocer las opciones, los diferentes formatos de salida, etc.

Thanatos
fuente
8
Eso no hace el trabajo solicitado; inserta un montón de caracteres adicionales, incluso con el uso de interruptores de línea de comando sugeridos en otras respuestas.
xenocyon
20

Considere esto:
archivo a.txt:

abcd
efgh

archivo b.txt:

abcd

Puedes encontrar la diferencia con:

diff -a --suppress-common-lines -y a.txt b.txt

La salida será:

efgh 

Puede redirigir la salida en un archivo de salida (c.txt) usando:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

Esto responderá a su pregunta:

"... que contiene las líneas en el archivo1 que no están presentes en el archivo2".

Neilvert Noval
fuente
2
Hay dos limitaciones para esta respuesta: (1) solo funciona para líneas cortas (menos de 80 caracteres por defecto, aunque esto se puede modificar) y, lo que es más importante, (2) agrega un "<" al final de cada línea que debe eliminarse con otro programa (por ejemplo, awk, sed).
sergut
En muchos casos, también querrá usar -d, lo que hará difftodo lo posible para encontrar la diferencia más pequeña posible. -i, -E, -w, -BY --suppress-blank-emptytambién puede ser útil en ocasiones, aunque no siempre. Si no sabe qué se ajusta a su caso de uso, intente diff --helpprimero (que generalmente es una buena idea cuando no sabe lo que puede hacer un comando).
Egor Hans
Además, al usar --line-format =% L, evita que diff genere caracteres adicionales (al menos, la ayuda dice que funciona así, pero está a punto de probarlo).
Egor Hans
Además, esto es más corto y parece funcionar de la misma manera stackoverflow.com/a/27667185/1179925
mrgloom
8

A veces diffes la utilidad que necesita, pero a veces joines más adecuada. Los archivos deben ordenarse previamente o, si está utilizando un shell que admita la sustitución de procesos como bash, ksh o zsh, puede ordenarlos sobre la marcha.

join -v 1 <(sort file1) <(sort file2)
Pausado hasta nuevo aviso.
fuente
¡Deberías conseguir una medalla por esto! Era exactamente lo que estaba buscando las últimas 2 horas
Zatarra
7

Tratar

sdiff file1 file2

Normalmente funciona mucho mejor en la mayoría de los casos para mí. Es posible que desee ordenar los archivos antes, si el orden de las líneas no es importante (por ejemplo, algunos archivos de configuración de texto).

Por ejemplo,

sdiff -w 185 file1.cfg file2.cfg
Tagar
fuente
1
¡Buena utilidad! Me encanta cómo marca las líneas diferenciadoras. Hace que sea mucho más fácil comparar configuraciones. Esto, junto con el tipo es un combo mortal (por ejemplo sdiff <(sort file1) <(sort file2))
jmagnusson
3

Si necesita resolver esto con coreutils, la respuesta aceptada es buena:

comm -23 <(sort file1) <(sort file2) > file3

También puede usar sd (stream diff), que no requiere clasificación ni sustitución de procesos y admite flujos infinitos, así:

cat file1 | sd 'cat file2' > file3

Probablemente no sea un gran beneficio en este ejemplo, pero aún así considérelo; en algunos casos no podrás utilizar commni grep -Fni diff.

Aquí hay una entrada de blog que escribí sobre las diferentes transmisiones en la terminal, que presenta sd.

mlg
fuente
3

Sin embargo, ¿no hay grepsolución?

  • líneas que existen solo en file2:

    grep -Fxvf file1 file2 > file3
  • líneas que existen solo en el archivo1:

    grep -Fxvf file2 file1 > file3
  • líneas que existen en ambos archivos:

    grep -Fxf file1 file2 > file3
αғsнιη
fuente
2

Muchas respuestas ya, pero ninguna de ellas perfecta en mi humilde opinión. La respuesta de Thanatos deja algunos caracteres adicionales por línea y la respuesta de Sorpigal requiere que los archivos se clasifiquen o preordenan, lo que puede no ser adecuado en todas las circunstancias.

Creo que la mejor manera de obtener las líneas que son diferentes y nada más (no hay caracteres adicionales, sin volver a realizar el pedido) es una combinación de diff, grepy awk(o similar).

Si las líneas no contienen ningún "<", una línea breve puede ser:

diff urls.txt* | grep "<" | sed 's/< //g'

pero eso eliminará todas las instancias de "<" (menos que, espacio) de las líneas, lo que no siempre está bien (por ejemplo, código fuente). La opción más segura es usar awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

Esta línea única diferencia ambos archivos, luego filtra la salida de estilo ed de diff, luego elimina el "<" final que agrega diff. Esto funciona incluso si las líneas contienen algunos "<".

sergut
fuente
1
comm no requiere clasificación (¿en versiones más recientes?), solo use --nocheck-order. Lo uso mucho cuando manipulo csvs desde la CLI
ak5
2

Me sorprende que nadie ha mencionado diff -ya producir una salida de lado a lado , por ejemplo:

diff -y file1 file2 > file3

Y en file3(las diferentes líneas tienen un símbolo |en el medio):

same     same
diff_1 | diff_2
xtluo
fuente
1

Use la utilidad Diff y extraiga solo las líneas que comienzan con <en la salida

Capslockk
fuente
0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

Probé casi todas las respuestas en este hilo, pero ninguna estaba completa. Después de algunos senderos arriba, uno funcionó para mí. diff le dará una diferencia pero con algunos charas especiales no deseados. donde las líneas de diferencia reales comienzan con '>'. así que el siguiente paso es que las líneas grep comiencen con '>' y luego las eliminen con sed .

tollin jose
fuente
1
Esta es una mala idea. También necesitaría modificar las líneas que comienzan con <. Verá esto si cambia el orden de los archivos de entrada. Incluso si hiciera esto, querría omitir grepusando más sed: `diff a1 a2 | sed '/> / s ///' `Esto todavía puede romper las líneas que contienen >o <en la situación correcta y todavía deja líneas adicionales que describen los números de línea. Si quería probar este enfoque de una mejor manera sería: diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'.
sorigal
0

Puede usar diffcon el siguiente formato de salida:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format='', deshabilite la salida para file1 si la línea es diferente compare en file2.
--unchanged-line-format='', deshabilite la salida si las líneas son las mismas.

αғsнιη
fuente