En informática , la utilidad diff es una herramienta de comparación de datos que calcula y muestra las diferencias entre los contenidos de los archivos. A diferencia de las nociones de distancia de edición utilizadas para otros fines, diff está orientada a líneas en lugar de a caracteres, pero es como la distancia de Levenshtein en el sentido de que intenta determinar el conjunto más pequeño de eliminaciones e inserciones para crear un archivo a partir de otro. La utilidad muestra los cambios en uno de varios formatos estándar, de modo que tanto los humanos como las computadoras puedan analizar los cambios y usarlos para aplicar parches .
Por lo general, diff se utiliza para mostrar los cambios entre dos versiones del mismo archivo. Las implementaciones modernas también admiten archivos binarios . [1] La salida se denomina "diff" o parche , ya que la salida se puede aplicar con el programa de Unix patch . La salida de utilidades de comparación de archivos similares también se denomina "diff"; al igual que el uso de la palabra " grep " para describir el acto de búsqueda, la palabra diff se convirtió en un término genérico para calcular la diferencia de datos y los resultados de la misma. [2] El estándar POSIX especifica el comportamiento de las utilidades "diff" y "patch" y sus formatos de archivo. [3]
diff se desarrolló a principios de la década de 1970 en el sistema operativo Unix, que estaba surgiendo de los Laboratorios Bell en Murray Hill, Nueva Jersey. Fue parte de la quinta edición de Unix lanzada en 1974, [4] y fue escrita por Douglas McIlroy y James Hunt . Esta investigación se publicó en un artículo de 1976 coescrito con James W. Hunt, quien desarrolló un prototipo inicial de diff . [5] El algoritmo que este artículo describió se conoció como el algoritmo Hunt-Szymanski .
El trabajo de McIlroy fue precedido e influenciado por el programa de comparación de Steve Johnson en GECOS y el programa de prueba de Mike Lesk . Proof también se originó en Unix y, como diff , produjo cambios línea por línea e incluso usó corchetes angulares (">" y "<") para presentar inserciones y eliminaciones de líneas en la salida del programa. Sin embargo, las heurísticas utilizadas en estas primeras aplicaciones se consideraron poco confiables. La utilidad potencial de una herramienta diff provocó que McIlroy investigara y diseñara una herramienta más robusta que pudiera usarse en una variedad de tareas, pero que funcionara bien en las limitaciones de procesamiento y tamaño del hardware del PDP-11 . Su enfoque del problema fue el resultado de la colaboración con personas de Bell Labs, incluidos Alfred Aho , Elliot Pinson, Jeffrey Ullman y Harold S. Stone.
En el contexto de Unix, el uso del editor de línea ed proporcionó a diff la capacidad natural de crear "scripts de edición" utilizables por máquina. Estos scripts de edición, cuando se guardan en un archivo, pueden, junto con el archivo original, ser reconstituidos por ed en el archivo modificado en su totalidad. Esto redujo en gran medida el almacenamiento secundario necesario para mantener múltiples versiones de un archivo. McIlroy consideró escribir un postprocesador para diff donde se pudieran diseñar e implementar una variedad de formatos de salida, pero encontró que era más frugal y simple que diff fuera responsable de generar la sintaxis y la entrada de orden inverso aceptada por el comando ed .
En 1984, Larry Wall creó una utilidad independiente, patch , y publicó su código fuente en los grupos de noticias mod.sources y net.sources . [6] [7] [8] Este programa modifica archivos utilizando la salida de diff y tiene la capacidad de coincidir con el contexto.
La Guía de portabilidad X/Open número 2 de 1987 incluye diferencias. El modo de contexto se agregó en POSIX.1-2001 (número 6). El modo unificado se agregó en POSIX.1-2008 (número 7). [9]
En los primeros años de diff , los usos comunes incluían comparar cambios en el código fuente del software y el marcado de documentos técnicos, verificar la salida de depuración del programa, comparar listados del sistema de archivos y analizar el código ensamblador de la computadora. La salida destinada a ed estaba motivada para proporcionar compresión para una secuencia de modificaciones realizadas a un archivo. [ cita requerida ] El Sistema de control de código fuente (SCCS) y su capacidad para archivar revisiones surgieron a fines de la década de 1970 como consecuencia del almacenamiento de scripts de edición de diff .
La operación de diff se basa en resolver el problema de la subsecuencia común más larga . [5]
En este problema, dadas dos secuencias de elementos:
a b c d f g h j q z
a b c d e f g yo j krxy z
y queremos encontrar la secuencia más larga de elementos que esté presente en ambas secuencias originales en el mismo orden. Es decir, queremos encontrar una nueva secuencia que se pueda obtener a partir de la primera secuencia original eliminando algunos elementos, y a partir de la segunda secuencia original eliminando otros elementos. También queremos que esta secuencia sea lo más larga posible. En este caso es
abcdfgjz
A partir de una subsecuencia común más larga, solo es necesario un pequeño paso para obtener un resultado similar a diff : si un elemento está ausente en la subsecuencia pero está presente en la primera secuencia original, debe haber sido eliminado (como se indica con las marcas '-', a continuación). Si está ausente en la subsecuencia pero está presente en la segunda secuencia original, debe haber sido insertado (como se indica con las marcas '+').
ehiqkrxy+ - + - + + + +
El diff
comando se invoca desde la línea de comandos, pasándole los nombres de dos archivos: . La salida del comando representa los cambios necesarios para transformar el archivo original en el nuevo archivo.diff original new
Si original y new son directorios, se ejecutará diff en cada archivo que exista en ambos directorios. Una opción, , descenderá recursivamente por cualquier subdirectorio coincidente para comparar archivos entre directorios.-r
Cualquiera de los ejemplos del artículo utiliza los dos archivos siguientes, original y nuevo :
En este formato de salida tradicional,asignifica añadido ,dpara eliminados ydopara cambiar . Los números de línea del archivo original aparecen antesa/d/doy las del nuevo archivo aparecen después. Los signos menor que y mayor que (al comienzo de las líneas que se agregan, eliminan o modifican) indican en qué archivo aparecen las líneas. Las líneas de adición se agregan al archivo original para que aparezcan en el nuevo archivo. Las líneas de eliminación se eliminan del archivo original para que no aparezcan en el nuevo archivo.
De manera predeterminada, no se muestran las líneas comunes a ambos archivos. Las líneas que se han movido se muestran como agregadas en su nueva ubicación y como eliminadas de su ubicación anterior. [10] Sin embargo, algunas herramientas de comparación resaltan las líneas movidas.
Las versiones modernas de diff aún pueden generar un script ed-e
con la opción . El script edit resultante para este ejemplo es el siguiente:
24 aEste párrafo contiene novedades importantes que se suman a este documento..17 c consulte este documento..11,15 d 0 a ¡ Este es un aviso importante! Por lo tanto, debe ubicarse al comienzo de este documento..
Para transformar el contenido del archivo original en el contenido del archivo new usando ed , debemos agregar dos líneas a este archivo diff, una línea que contenga un comando (write) y otra que contenga un comando (quit) (por ejemplo, by ). Aquí le dimos al archivo diff el nombre mydiff y la transformación se realizará cuando ejecutemos .w
q
printf "w\nq\n" >> mydiff
ed -s original < mydiff
La distribución Berkeley de Unix se encargó de agregar el formato de contexto ( -c
) y la capacidad de recursar en estructuras de directorio del sistema de archivos ( -r
), agregando esas características en 2.8 BSD, lanzado en julio de 1981. El formato de contexto de diff introducido en Berkeley ayudó a distribuir parches para el código fuente que puede haber sido modificado mínimamente.
En el formato de contexto, las líneas modificadas se muestran junto con las líneas que no han cambiado antes y después. La inclusión de cualquier número de líneas que no han cambiado proporciona un contexto al parche. El contexto consta de líneas que no han cambiado entre los dos archivos y sirve como referencia para localizar el lugar de las líneas en un archivo modificado y encontrar la ubicación prevista para que se aplique un cambio independientemente de si los números de línea siguen correspondiendo. El formato de contexto introduce una mayor legibilidad para los humanos y fiabilidad al aplicar el parche, y una salida que se acepta como entrada para el programa de parches . Este comportamiento inteligente no es posible con la salida diff tradicional.
El usuario puede definir la cantidad de líneas sin cambios que se muestran arriba y debajo de un fragmento de cambio , incluso cero, pero tres líneas es el valor predeterminado. Si el contexto de líneas sin cambios en un fragmento se superpone con un fragmento adyacente, diff evitará duplicar las líneas sin cambios y fusionará los fragmentos en uno solo.
Un " !
" representa un cambio entre líneas que corresponden en los dos archivos, mientras que un " +
" representa la adición de una línea y un " -
" la eliminación de una línea. Un espacio en blanco representa una línea sin cambios. Al comienzo del parche se encuentra la información del archivo, incluida la ruta completa y una marca de tiempo delimitada por un carácter de tabulación. Al comienzo de cada fragmento se encuentran los números de línea que se aplican para el cambio correspondiente en los archivos. Un rango de números que aparece entre conjuntos de tres asteriscos se aplica al archivo original, mientras que los conjuntos de tres guiones se aplican al nuevo archivo. Los rangos de fragmentos especifican los números de línea inicial y final en el archivo respectivo.
El comando diff -c original new
produce el siguiente resultado:
*** /ruta/a/la/marca/de/tiempo original --- /ruta/a/la/nueva/marca/de/tiempo****************** 1,3 ****--- 1,9 ---- + ¡Este es un aviso + importante ! ¡Por lo tanto, debería + estar ubicado al + comienzo de este + documento! + Esta parte del documento se ha mantenido igual de una versión a otra.****************** 8,20 **** comprimir el tamaño de los cambios.- Este párrafo contiene - texto que no está actualizado. - Se eliminará en un futuro próximo. ¡Es importante la ortografía ! Revisa este documento. Por otro lado, una palabra mal escrita no es el fin del mundo. --- 14,21 ---- comprime el tamaño de los cambios. ¡Es importante saber escribir bien ! Revisa este documento. Por otro lado, una palabra mal escrita no es el fin del mundo.****************** 22,24 ****--- 23,29 ---- Este párrafo necesita ser modificado. Se pueden agregar cosas después de él. + + Este párrafo contiene + nuevas incorporaciones importantes + a este documento.
Nota : Aquí, la salida de diff se muestra con colores para facilitar su lectura. La utilidad diff no produce una salida en color; su salida es texto sin formato . Sin embargo, muchas herramientas pueden mostrar la salida con colores mediante el resaltado de sintaxis .
El formato unificado (o unidiff ) [11] [12] hereda las mejoras técnicas realizadas por el formato de contexto, pero produce una diferencia más pequeña con el texto antiguo y el nuevo presentados inmediatamente adyacentes. El formato unificado se invoca generalmente utilizando la opción de línea de comandos-u
" " . Esta salida se utiliza a menudo como entrada para el programa de parches . Muchos proyectos solicitan específicamente que los "diffs" se envíen en el formato unificado, lo que hace que el formato de diferencia unificado sea el formato más común para el intercambio entre desarrolladores de software.
Los diffs de contexto unificados fueron desarrollados originalmente por Wayne Davison en agosto de 1990 (en unidiff , que apareció en el Volumen 14 de comp.sources.misc). Richard Stallman agregó soporte para diffs unificados a la utilidad diff del Proyecto GNU un mes después, y la característica debutó en GNU diff 1.15, publicada en enero de 1991. Desde entonces, GNU diff ha generalizado el formato de contexto para permitir el formato arbitrario de los diffs.
El formato comienza con el mismo encabezado de dos líneas que el formato de contexto, excepto que el archivo original está precedido por "---" y el nuevo archivo está precedido por "+++". A continuación se encuentran uno o más fragmentos de cambio que contienen las diferencias de línea en el archivo. Las líneas contextuales sin cambios están precedidas por un carácter de espacio, las líneas de adición están precedidas por un signo más y las líneas de eliminación están precedidas por un signo menos .
Un fragmento comienza con información de rango y va seguido inmediatamente de las adiciones y eliminaciones de líneas y cualquier cantidad de líneas contextuales. La información de rango está rodeada por dobles arrobas y combina en una sola línea lo que aparece en dos líneas en el formato de contexto (arriba). El formato de la línea de información de rango es el siguiente:
@@ -l,s +l,s @@ encabezado de sección opcional
La información del rango de fragmentos contiene dos rangos de fragmentos. El rango del fragmento del archivo original está precedido por un símbolo menos, y el rango del nuevo archivo está precedido por un símbolo más. Cada rango de fragmentos tiene el formato l,s , donde l es el número de línea inicial y s es el número de líneas al que se aplica el fragmento de cambio para cada archivo respectivo. En muchas versiones de GNU diff, cada rango puede omitir la coma y el valor final s , en cuyo caso s toma el valor predeterminado 1. Tenga en cuenta que el único valor realmente interesante es el número de línea l del primer rango; todos los demás valores se pueden calcular a partir de la diferencia.
El rango de fragmentos del original debe ser la suma de todas las líneas de fragmentos contextuales y eliminadas (incluidas las modificadas). El rango de fragmentos del nuevo archivo debe ser la suma de todas las líneas de fragmentos contextuales y agregadas (incluidas las modificadas). Si la información del tamaño del fragmento no se corresponde con la cantidad de líneas del fragmento, la diferencia podría considerarse inválida y rechazarse.
Opcionalmente, el rango de fragmentos puede ir seguido del encabezado de la sección o función de la que forma parte el fragmento. Esto es útil principalmente para facilitar la lectura de la diferencia. Al crear una diferencia con GNU diff, el encabezado se identifica mediante la coincidencia de expresiones regulares . [13]
Si se modifica una línea, se representa como una eliminación y una adición. Dado que los fragmentos del archivo original y el nuevo aparecen en el mismo fragmento, dichos cambios aparecerían adyacentes entre sí. [14] Una ocurrencia de esto en el ejemplo siguiente es:
-Consulta este documento.+Consulta este documento.
El comando diff -u original new
produce el siguiente resultado:
--- /ruta/a/la/marca/de/tiempo/original +++ /ruta/a/la/nueva marca/de/tiempo @@ -1,3 +1,9 @@ +¡Este es un aviso importante! ¡Por lo tanto, debería estar ubicado al principio de este documento! + Esta parte del documento se ha mantenido igual desde la versión hasta @@ -8,13 +14,8 @@ comprimir el tamaño de los cambios.-Este párrafo contiene texto desactualizado. -Será eliminado en un futuro cercano. -Es importante revisar la ortografía de este documento. +Revisar este documento. Por otro lado, una palabra mal escrita no es el fin del mundo. @@ -22,3 +23,7 @@ Este párrafo necesita ser cambiado. Se pueden agregar cosas después de él. + +Este párrafo contiene +nuevas incorporaciones importantes +a este documento.
Nota : Aquí, la salida de diff se muestra con colores para facilitar su lectura. La utilidad diff no produce una salida en color; su salida es texto sin formato . Sin embargo, muchas herramientas pueden mostrar la salida con colores mediante el resaltado de sintaxis .
Tenga en cuenta que para separar correctamente los nombres de los archivos de las marcas de tiempo, el delimitador entre ellos es un carácter de tabulación. Esto es invisible en la pantalla y se puede perder cuando se copian y pegan las diferencias desde las pantallas de la consola o la terminal.
Existen algunas modificaciones y extensiones de los formatos diff que son utilizados y comprendidos por ciertos programas y en ciertos contextos. Por ejemplo, algunos sistemas de control de versiones (como Subversion) especifican un número de versión, una "copia de trabajo" o cualquier otro comentario en lugar de una marca de tiempo o además de esta en la sección de encabezado del diff.
Algunas herramientas permiten fusionar las diferencias de varios archivos diferentes en uno solo, utilizando un encabezado para cada archivo modificado que puede verse así:
Índice: ruta/al/archivo.cpp
El caso especial de los archivos que no terminan en una nueva línea no se maneja. Ni la utilidad unidiff ni el estándar POSIX diff definen una forma de manejar este tipo de archivos. (De hecho, dichos archivos no son archivos de "texto" según las definiciones estrictas de POSIX. [15] ) GNU diff y git producen "\ No newline at end of file" (o una versión traducida) como diagnóstico, pero este comportamiento no es portable. [16] GNU patch no parece manejar este caso, mientras que git-apply sí. [17]
El programa patch no necesariamente reconoce la salida diff específica de la implementación. Sin embargo, se sabe que GNU patch reconoce los parches git y actúa de manera un poco diferente. [18]
Los cambios desde 1975 incluyen mejoras en el algoritmo central, la adición de características útiles al comando y el diseño de nuevos formatos de salida. El algoritmo básico se describe en los artículos An O(ND) Difference Algorithm and its Variations de Eugene W. Myers [19] y en A File Comparison Program de Webb Miller y Myers. [20] El algoritmo fue descubierto y descrito de forma independiente en Algorithms for Approximate String Matching , de Esko Ukkonen . [21] Las primeras ediciones del programa diff se diseñaron para comparaciones de líneas de archivos de texto esperando que el carácter de nueva línea delimitara las líneas. En la década de 1980, el soporte para archivos binarios resultó en un cambio en el diseño e implementación de la aplicación.
GNU diff y diff3 están incluidos en el paquete diffutils junto con otras utilidades relacionadas con diff y parches . [22]
Los posprocesadores sdiff y diffmk generan listas de diferencias en paralelo y marcas de cambio aplicadas a documentos impresos, respectivamente. Ambos fueron desarrollados en otros laboratorios Bell en 1981 o antes. [ cita requerida ] [ discutir ]
Diff3 compara un archivo con otros dos archivos mediante la conciliación de dos diffs. Fue concebido originalmente por Paul Jensen para conciliar los cambios realizados por dos personas que editaban una fuente común. También lo utilizan los sistemas de control de revisión, por ejemplo RCS , para fusionar archivos . [23]
Emacs utiliza Ediff para mostrar los cambios que proporcionaría un parche en una interfaz de usuario que combina edición interactiva y capacidades de fusión de archivos de parche.
Vim proporciona vimdiff para comparar de dos a ocho archivos, con las diferencias resaltadas en color. [24] Si bien históricamente invocaba el programa diff, el vim moderno usa la bifurcación de git del código de la biblioteca xdiff (LibXDiff), lo que proporciona una velocidad y una funcionalidad mejoradas. [25]
GNU Wdiff [26] es una interfaz para diff que muestra las palabras o frases que cambiaron en un documento de texto en lenguaje escrito incluso en presencia de ajuste de palabras o diferentes anchos de columna.
colordiff es un contenedor de Perl para 'diff' y produce la misma salida pero con coloración para los bits agregados y eliminados. [27] diff-so-fancy y diff-highlight son análogos más nuevos. [28] "delta" es una reescritura de Rust que resalta los cambios y el código subyacente al mismo tiempo. [29]
Patchutils contiene herramientas que combinan, reorganizan, comparan y corrigen diferencias de contexto y diferencias unificadas. [30]
Las utilidades que comparan archivos fuente por su estructura sintáctica se han creado principalmente como herramientas de investigación para algunos lenguajes de programación; [31] [32] [33] algunas están disponibles como herramientas comerciales. [34] [35] Además, las herramientas gratuitas que realizan comparaciones teniendo en cuenta la sintaxis incluyen:
spiff es una variante de diff que ignora las diferencias en los cálculos de punto flotante con errores de redondeo y espacios en blanco , ambos generalmente irrelevantes para la comparación de código fuente. Bellcore escribió la versión original. [41] [42] Un puerto HPUX es la versión pública más actual. spiff no admite archivos binarios. spiff genera la salida estándar en formato diff estándar y acepta entradas en los lenguajes de programación C , Bourne shell , Fortran , Modula-2 y Lisp . [43] [44] [41] [45] [42]
LibXDiff es una biblioteca LGPL que proporciona una interfaz para muchos algoritmos desde 1998. Originalmente se implementó un algoritmo Myers mejorado con huella dactilar de Rabin (a partir de la versión final de 2008), [46] pero desde entonces Git y la bifurcación de libgit2 han ampliado el repositorio con muchos de los suyos. Un algoritmo llamado "histograma" se considera generalmente mucho mejor que el algoritmo Myers original, tanto en velocidad como en calidad. [47] [48] Esta es la versión moderna de LibXDiff que utiliza Vim. [25]
En las diferencias de estilo git, el estado "antes" de cada parche se refiere al estado inicial antes de modificar cualquier archivo.
La forma más sencilla de comenzar a editar en modo diff es con el comando "vimdiff". Esto inicia Vim como de costumbre y, además, configura la visualización de las diferencias entre los argumentos.
Esto es equivalente a:
vimdiff file1 file2 [file3] [file4] [...file8]
vim -d file1 file2 [file3] [file4] [...file8]
Esto demuestra que la diferencia de histograma supera ligeramente a Myers, mientras que la paciencia es mucho más lenta que los demás.