Cómo comparar dos archivos

83

Básicamente, lo que quiero hacer es comparar dos archivos por línea por columna 2. ¿Cómo podría lograr esto?

Archivo_1.txt:

User1 US
User2 US
User3 US

Archivo_2.txt:

User1 US
User2 US
User3 NG

Archivo de salida:

User3 has changed
Roboman1723
fuente
11
Usodiff "File_1.txt" "File_2.txt"
Pandya el
También visite: askubuntu.com/q/12473
Pandya

Respuestas:

92

Mira el diffcomando. Es una buena herramienta, y puede leer todo al escribir man diffen su terminal.

El comando que querrás hacer es el diff File_1.txt File_2.txtque generará la diferencia entre los dos y debería verse así:

ingrese la descripción de la imagen aquí

Una nota rápida sobre la lectura del resultado del tercer comando: las 'flechas' ( <y >) se refieren a cuál es el valor de la línea en el archivo izquierdo ( <) frente al archivo derecho ( >), siendo el archivo izquierdo el que ingresó primero en la línea de comando, en este casoFile_1.txt

Además, es posible que observe que el cuarto comando es que diff ... | tee Output_Filecanaliza los resultados de diffa tee, que luego coloca esa salida en un archivo, para que pueda guardarlo para más adelante si no desea verlo todo en la consola en ese momento.

Mitch
fuente
¿Esto puede hacer otros archivos (como imágenes)? ¿O se limita solo a documentos?
Gregory Opera
2
Que yo sepa, se limita a los archivos de texto. El código funcionará, ya que es esencialmente texto, pero cualquier archivo binario (que son las imágenes) simplemente se eliminará. Puede comparar para ver si son idénticos haciendo: diff file1 file2 -s. Aquí hay un ejemplo: imgur.com/ShrQx9x
Mitch
¿Hay alguna manera de colorear la salida? Me gustaría mantenerlo solo en CLI, pero con algo más ... toque humano.
Lazar Ljubenović
36

O puedes usar Meld Diff

Meld te ayuda a comparar archivos, directorios y proyectos controlados por versiones. Proporciona una comparación de dos y tres vías de archivos y directorios, y tiene soporte para muchos sistemas de control de versiones populares.

Instalar ejecutando:

sudo apt-get install meld

Su ejemplo:

ingrese la descripción de la imagen aquí

Comparar directorio:

ingrese la descripción de la imagen aquí

Ejemplo con texto completo:

ingrese la descripción de la imagen aquí

Achu
fuente
18

Puedes usar vimdiff .

Ejemplo:

vimdiff  file1  file2
Señora
fuente
1
este tiene colores
Jake Toronto
Esto me ayudó, ya que mostró que el final de línea de mi primer archivo estaba en dosy el segundo en unix.
LoMaPh
13

FWIW, me gusta bastante lo que obtengo con la salida de lado a lado de diff

diff -y -W 120 File_1.txt File_2.txt

daría algo como:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
Mike Reardon
fuente
10

Puedes usar el comando cmp:

cmp -b "File_1.txt" "File_2.txt"

la salida sería

a b differ: byte 25, line 3 is 125 U 116 N
Maythux
fuente
cmpes mucho más rápido que diffsi todo lo que quieres es el código de retorno.
stevesliva
8

MeldEs una herramienta realmente genial. Pero también puede usar diffusepara comparar visualmente dos archivos:

diffuse file1.txt file2.txt

ingrese la descripción de la imagen aquí

Meysam
fuente
7

Literalmente apegado a la pregunta (archivo1, archivo2, archivo de salida con el mensaje "ha cambiado") el script a continuación funciona.

Copie el script en un archivo vacío, guárdelo como compare.py, hágalo ejecutable, ejecútelo con el comando:

/path/to/compare.py <file1> <file2> <outputfile>

La secuencia de comandos:

#!/usr/bin/env python

import sys
file1 = sys.argv[1]; file2 = sys.argv[2]; outfile = sys.argv[3]

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

with open(outfile, "wt") as out:
    for line in mismatch:
        out.write(line+" has changed"+"\n")

Con unas pocas líneas adicionales, puede hacer que se imprima en un archivo de salida o en el terminal, dependiendo de si el archivo de salida está definido:

Para imprimir en un archivo:

/path/to/compare.py <file1> <file2> <outputfile>

Para imprimir en la ventana de terminal:

/path/to/compare.py <file1> <file2> 

La secuencia de comandos:

#!/usr/bin/env python

import sys

file1 = sys.argv[1]; file2 = sys.argv[2]
try:
    outfile = sys.argv[3]
except IndexError:
    outfile = None

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

if outfile != None:
        with open(outfile, "wt") as out:
            for line in mismatch:
                out.write(line+" has changed"+"\n")
else:
    for line in mismatch:
        print line+" has changed"
Jacob Vlijm
fuente
4

Una manera fácil es usarla colordiff, que se comporta como diffpero colorea su salida. Esto es muy útil para leer diferencias. Usando tu ejemplo,

$ colordiff -u File_1.txt File_2.txt
--- File_1.txt  2016-12-24 17:59:17.409490554 -0500
+++ File_2.txt  2016-12-24 18:00:06.666719659 -0500
@@ -1,3 +1,3 @@
 User1 US
 User2 US
-User3 US
+User3 NG

donde la uopción da una diferencia unificada. Así es como se ve la diferencia coloreada:

ingrese la descripción de la imagen aquí

Instalar colordiffejecutando sudo apt-get install colordiff.

edwinksl
fuente
1
Si quieres colores, creo que el diff incorporado en vim es realmente fácil de usar, como en la respuesta de Mr.S
thomasrutter
2

Respuesta adicional

Si no hay necesidad de saber qué partes de los archivos difieren, puede usar la suma de comprobación del archivo. Hay muchas formas de hacerlo, usando md5sumo sha256sum. Básicamente, cada uno de ellos genera una cadena en la que un archivo contiene hash. Si los dos archivos son iguales, su hash también será el mismo. Esto se usa a menudo cuando descarga software, como imágenes iso de instalación de Ubuntu. A menudo se usan para verificar la integridad de un contenido descargado.

Considere la secuencia de comandos a continuación, donde puede dar dos archivos como argumentos, y el archivo le dirá si son iguales o no.

#!/bin/bash

# Check if both files exist  
if ! [ -e "$1"  ];
then
    printf "%s doesn't exist\n" "$1"
    exit 2
elif ! [ -e "$2" ]
then
    printf "%s doesn't exist\n" "$2"
    exit 2
fi

# Get checksums of eithe file
file1_sha=$( sha256sum "$1" | awk '{print $1}')
file2_sha=$( sha256sum "$2" | awk '{print $1}')

# Compare the checksums
if [ "x$file1_sha" = "x$file2_sha" ]
then
    printf "Files %s and %s are the same\n" "$1" "$2"
    exit 0
else
    printf "Files %s and %s are different\n" "$1" "$2"
    exit 1
fi

Ejecución de muestra:

$ ./compare_files.sh /etc/passwd ./passwd_copy.txt                                                                
Files /etc/passwd and ./passwd_copy.txt are the same
$ echo $?
0
$ ./compare_files.sh /etc/passwd /etc/default/grub                                                                
Files /etc/passwd and /etc/default/grub are different
$ echo $?
1

Respuesta anterior

Además, hay un commcomando, que compara dos archivos ordenados, y da salida en 3 columnas: columna 1 para elementos únicos para el archivo # 1, columna 2 para elementos únicos para el archivo # 2 y columna 3 para elementos presentes en ambos archivos.

Para suprimir cualquiera de las columnas, puede usar los modificadores -1, -2 y -3. El uso de -3 mostrará las líneas que difieren.

A continuación puede ver la captura de pantalla del comando en acción.

ingrese la descripción de la imagen aquí

Solo hay un requisito: los archivos deben ordenarse para que se puedan comparar correctamente. sortEl comando se puede utilizar para ese propósito. A continuación se muestra otra captura de pantalla, donde los archivos se ordenan y luego se comparan. Las líneas que comienzan en la campana izquierda son solo para File_1, las líneas que comienzan en la columna 2 pertenecen solo a File_2

ingrese la descripción de la imagen aquí

Sergiy Kolodyazhnyy
fuente
@DavidFoerster es un poco difícil de editar en dispositivos móviles :) Hecho ahora, sin embargo
Sergiy Kolodyazhnyy
2

Instalar git y usar

$ git diff filename1 filename2

Y obtendrá la salida en un bonito formato de color

Instalación de Git

$ apt-get update
$ apt-get install git-core
Eric Korolev
fuente
2

colcmp.sh

Compara pares de nombre / valor en 2 archivos en el formato name value\n. Escribe el namepara Output_filesi ha cambiado. Requiere bash v4 + para matrices asociativas .

Uso

$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2

Archivo de salida

$ cat Output_File
User3 has changed

Fuente (colcmp.sh)

cmp -s "$1" "$2"
case "$?" in
    0)
        echo "" > Output_File
        echo "files are identical"
        ;;
    1)
        echo "" > Output_File
        cp "$1" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.array1.tmp.sh
        chmod 755 ~/.colcmp.array1.tmp.sh
        declare -A A1
        source ~/.colcmp.array1.tmp.sh

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

        USERSWHODIDNOTCHANGE=
        for i in "${!A1[@]}"; do
            if [ "${A2[$i]+x}" = "" ]; then
                echo "$i was removed"
                echo "$i has changed" > Output_File
            fi
        done
        for i in "${!A2[@]}"; do
            if [ "${A1[$i]+x}" = "" ]; then
                echo "$i was added as '${A2[$i]}'"
                echo "$i has changed" > Output_File
            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
                echo "$i changed from '${A1[$i]}' to '${A2[$i]}'"
                echo "$i has changed" > Output_File
            else
                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
            fi
        done
        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
        ;;
    *)
        echo "error: file not found, access denied, etc..."
        echo "usage: ./colcmp.sh File_1.txt File_2.txt"
        ;;
esac

Explicación

Desglose del código y lo que significa, a mi entender. Agradezco las ediciones y sugerencias.

Comparación de archivos básicos

cmp -s "$1" "$2"
case "$?" in
    0)
        # match
        ;;
    1)
        # compare
        ;;
    *)
        # error
        ;;
esac

cmp establecerá el valor de $? como sigue :

  • 0 = coincidencia de archivos
  • 1 = los archivos difieren
  • 2 = error

Decidí usar un caso ... esa declaración para evaluar $? porque el valor de $? cambia después de cada comando, incluida la prueba ([).

Alternativamente, ¿podría haber usado una variable para mantener el valor de $? :

cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
    # match
elif [ $CMPRESULT -eq 1 ]; then
    # compare
else
    # error
fi

Arriba hace lo mismo que la declaración del caso. IDK que me gusta más.

Borrar la salida

        echo "" > Output_File

Arriba borra el archivo de salida, por lo que si ningún usuario ha cambiado, el archivo de salida estará vacío.

Hago esto dentro de las declaraciones de caso para que el Output_file permanezca sin cambios en caso de error.

Copiar archivo de usuario a la secuencia de comandos de Shell

        cp "$1" ~/.colcmp.arrays.tmp.sh

Arriba copia File_1.txt al directorio de inicio del usuario actual.

Por ejemplo, si el usuario actual es john, lo anterior sería lo mismo que cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Escapar de personajes especiales

Básicamente, soy paranoico. Sé que estos caracteres podrían tener un significado especial o ejecutar un programa externo cuando se ejecutan en un script como parte de la asignación de variables:

  • `- back-tick - ejecuta un programa y la salida como si la salida fuera parte de su script
  • $ - signo de dólar - generalmente prefija una variable
  • $ {}: permite una sustitución de variables más compleja
  • $ () - idk lo que hace pero creo que puede ejecutar código

Lo que no sé es cuánto no sé sobre bash. No sé qué otros personajes podrían tener un significado especial, pero quiero escapar de todos con una barra invertida:

        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh

sed puede hacer mucho más que la coincidencia de patrones de expresión regular . El patrón de script "s / (find) / (replace) /" realiza específicamente la coincidencia del patrón.

"s / (buscar) / (reemplazar) / (modificadores)"

en inglés: capturar cualquier puntuación o carácter especial como caputure group 1 (\\ 1)

  • (reemplazar) = \\\\\\ 1
    • \\\\ = carácter literal (\\) es decir, una barra invertida
    • \\ 1 = captura grupo 1

en inglés: prefija todos los caracteres especiales con una barra invertida

  • (modificadores) = g
    • g = reemplazar globalmente

en inglés: si se encuentra más de una coincidencia en la misma línea, reemplácelas todas

Comente el guión completo

        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh

El anterior usa una expresión regular para prefijar cada línea de ~ / .colcmp.arrays.tmp.sh con un carácter de comentario bash ( # ). Hago esto porque más tarde tengo la intención de ejecutar ~ / .colcmp.arrays.tmp.sh usando el comando de origen y porque no sé con seguridad el formato completo de File_1.txt .

No quiero ejecutar accidentalmente código arbitrario. No creo que nadie lo haga.

"s / (buscar) / (reemplazar) /"

en inglés: captura cada línea como caputure group 1 (\\ 1)

  • (reemplazar) = # \\ 1
    • # = carácter literal (#), es decir, un símbolo de libra o hash
    • \\ 1 = captura grupo 1

en inglés: reemplace cada línea con un símbolo de libra seguido de la línea que fue reemplazada

Convertir valor de usuario a A1 [Usuario] = "valor"

        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh

Arriba está el núcleo de este script.

  • convertir esto: #User1 US
    • a esto: A1[User1]="US"
    • o esto: A2[User1]="US"(para el segundo archivo)

"s / (buscar) / (reemplazar) /"

en inglés:

  • requiere pero ignora los caracteres de comentario principales (#)
  • ignorar espacios en blanco iniciales
  • captura la primera palabra como caputure group 1 (\\ 1)
  • requieren un espacio (o tabulación, o espacio en blanco)
    • eso será reemplazado con un signo igual porque
    • no es parte de ningún grupo de captura, y porque
    • el patrón (reemplazar) coloca un signo igual entre el grupo de captura 1 y el grupo de captura 2
  • captura el resto de la línea como grupo de captura 2

  • (reemplazar) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"

    • A1 \\ [- caracteres literales A1[para iniciar la asignación de matriz en una matriz llamadaA1
    • \\ 1 = grupo de captura 1, que no incluye el hash principal (#) y no incluye espacios en blanco iniciales, en este caso el grupo de captura 1 se está utilizando para establecer el nombre del par nombre / valor en la matriz asociativa bash.
    • \\] = \ "= caracteres literales ]="
      • ]= asignación de matriz cerrada, por ejemplo, A1[Usuario1 ]="US"
      • = = operador de asignación, por ejemplo, variable = valor
      • " = valor de cotización para capturar espacios ... aunque ahora que lo pienso, hubiera sido más fácil dejar que el código anterior reduzca todo a los caracteres de espacio.
    • \\ 1 = grupo de captura 2: en este caso, el valor del par nombre / valor
    • "= valor de cotización de cierre para capturar espacios

en inglés: reemplace cada línea en el formato #name valuecon un operador de asignación de matriz en el formatoA1[name]="value"

Hacer ejecutable

        chmod 755 ~/.colcmp.arrays.tmp.sh

El anterior usa chmod para hacer que el archivo de script de matriz sea ejecutable.

No estoy seguro si esto es necesario.

Declarar matriz asociativa (bash v4 +)

        declare -A A1

La A mayúscula indica que las variables declaradas serán matrices asociativas .

Es por eso que el script requiere bash v4 o superior.

Ejecute nuestro script de asignación variable de matriz

        source ~/.colcmp.arrays.tmp.sh

Ya lo tenemos:

  • convirtió nuestro archivo de líneas de User valuea líneas de A1[User]="value",
  • lo hizo ejecutable (tal vez) y
  • declarado A1 como una matriz asociativa ...

Por encima de que la fuente de la secuencia de comandos para ejecutar en el shell actual. Hacemos esto para poder mantener los valores variables que establece el script. Si ejecuta el script directamente, genera un nuevo shell, y los valores de las variables se pierden cuando el nuevo shell sale, o al menos eso entiendo.

Esto debería ser una función

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

Hacemos lo mismo por $ 1 y A1 que por $ 2 y A2 . Realmente debería ser una función. Creo que en este momento este script es lo suficientemente confuso y funciona, así que no lo arreglaré.

Detectar usuarios eliminados

        for i in "${!A1[@]}"; do
            # check for users removed
        done

Bucles anteriores a través de claves de matriz asociativas

            if [ "${A2[$i]+x}" = "" ]; then

Lo anterior utiliza la sustitución de variables para detectar la diferencia entre un valor no establecido frente a una variable que se ha establecido explícitamente en una cadena de longitud cero.

Aparentemente, hay muchas maneras de ver si se ha establecido una variable . Elegí el que tenía más votos.

                echo "$i has changed" > Output_File

Arriba agrega el usuario $ i al Output_File

Detectar usuarios agregados o modificados

        USERSWHODIDNOTCHANGE=

Arriba borra una variable para que podamos hacer un seguimiento de los usuarios que no cambiaron.

        for i in "${!A2[@]}"; do
            # detect users added, changed and not changed
        done

Bucles anteriores a través de claves de matriz asociativas

            if ! [ "${A1[$i]+x}" != "" ]; then

El anterior usa la sustitución de variables para ver si se ha establecido una variable .

                echo "$i was added as '${A2[$i]}'"

Como $ i es la clave de matriz (nombre de usuario) $ A2 [$ i] debería devolver el valor asociado con el usuario actual de File_2.txt .

Por ejemplo, si $ i es Usuario1 , lo anterior se lee como $ {A2 [Usuario1]}

                echo "$i has changed" > Output_File

Arriba agrega el usuario $ i al Output_File

            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then

Como $ i es la clave de matriz (nombre de usuario) $ A1 [$ i] debería devolver el valor asociado con el usuario actual de File_1.txt , y $ A2 [$ i] debería devolver el valor de File_2.txt .

Arriba compara los valores asociados para el usuario $ i de ambos archivos.

                echo "$i has changed" > Output_File

Arriba agrega el usuario $ i al Output_File

                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"

Arriba crea una lista separada por comas de usuarios que no cambiaron. Tenga en cuenta que no hay espacios en la lista, de lo contrario, se deberá citar la siguiente comprobación.

        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi

Arriba se informa el valor de $ USERSWHODIDNOTCHANGE pero solo si hay un valor en $ USERSWHODIDNOTCHANGE . La forma en que esto está escrito, $ USERSWHODIDNOTCHANGE no puede contener espacios. Si necesita espacios, lo anterior podría reescribirse de la siguiente manera:

        if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
Jonathan
fuente