Comment comparer deux fichiers

83

Donc, fondamentalement, ce que je veux faire est de comparer deux fichiers ligne par colonne 2. Comment pourrais-je accomplir cela?

Fichier_1.txt:

User1 US
User2 US
User3 US

Fichier_2.txt:

User1 US
User2 US
User3 NG

Fichier de sortie:

User3 has changed
Roboman1723
la source
11
Utilisationdiff "File_1.txt" "File_2.txt"
Pandya
Visitez également: askubuntu.com/q/12473
Pandya

Réponses:

92

Regardez dans la diffcommande. C'est un bon outil, et vous pouvez tout lire en tapant man diffdans votre terminal.

La commande que vous voudrez faire est celle diff File_1.txt File_2.txtqui affichera la différence entre les deux et devrait ressembler à ceci:

entrez la description de l'image ici

Une note rapide sur la lecture du résultat de la troisième commande: Les "flèches" ( <et >) font référence à la valeur de la ligne dans le fichier de gauche ( <) par rapport au fichier de droite ( >), le fichier de gauche étant celui que vous avez entré. d'abord sur la ligne de commande, dans ce casFile_1.txt

En outre, vous remarquerez peut-être que la 4ème commande diff ... | tee Output_Filerenvoie les résultats diffdans a tee, qui place ensuite cette sortie dans un fichier, de sorte que vous puissiez l'enregistrer pour plus tard si vous ne souhaitez pas tout afficher sur la console à la seconde près.

Mitch
la source
Est-ce que cela peut faire d’autres fichiers (comme des images)? Ou est-ce limité aux seuls documents?
Gregory Opera
2
Autant que je sache, c'est limité aux fichiers texte. Le code fonctionnera, car il s’agit essentiellement de texte, mais tous les fichiers binaires (qu’il s’agit d’images) seront simplement débloqués. Vous pouvez comparer pour voir si elles sont identiques en faisant: diff file1 file2 -s. Voici un exemple: imgur.com/ShrQx9x
Mitch
Y at-il un moyen de coloriser la sortie? J'aimerais conserver uniquement l'interface CLI, mais avec encore plus ... de contact humain.
Lazar Ljubenović
36

Ou vous pouvez utiliser Meld Diff

Meld vous aide à comparer des fichiers, des répertoires et des projets à version contrôlée. Il fournit une comparaison à deux et à trois voies des fichiers et des répertoires et prend en charge de nombreux systèmes de contrôle de version populaires.

Installez en exécutant:

sudo apt-get install meld

Votre exemple:

entrez la description de l'image ici

Comparer le répertoire:

entrez la description de l'image ici

Exemple avec plein de texte:

entrez la description de l'image ici

Achu
la source
18

Vous pouvez utiliser vimdiff .

Exemple:

vimdiff  file1  file2
Madame
la source
1
celui-ci a des couleurs
Jake Toronto le
Cela m'a aidé car il a montré que la fin de la ligne de mon premier fichier était en doset une seconde en unix.
LoMaPh
13

FWIW, j'aime bien ce que j'ai avec la sortie côte à côte de diff

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

donnerait quelque chose comme:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
Mike Reardon
la source
10

Vous pouvez utiliser la commande cmp:

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

la sortie serait

a b differ: byte 25, line 3 is 125 U 116 N
Maythux
la source
cmpest beaucoup plus rapide que diffsi tout ce que vous voulez, c'est le code de retour.
Stevesliva
8

Meldest un très bon outil. Mais vous pouvez également utiliser diffusepour comparer visuellement deux fichiers:

diffuse file1.txt file2.txt

entrez la description de l'image ici

Meysam
la source
7

Si vous vous en tenez à la question (fichier1, fichier2, fichier de sortie avec le message "a changé"), le script ci-dessous fonctionne.

Copiez le script dans un fichier vide, enregistrez-le sous compare.py, rendez-le exécutable, exécutez-le à l'aide de la commande suivante:

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

Le scénario:

#!/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")

Avec quelques lignes supplémentaires, vous pouvez le faire soit imprimer dans un fichier de sortie, soit dans le terminal, selon que le fichier de sortie est défini:

Pour imprimer dans un fichier:

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

Pour imprimer dans la fenêtre du terminal:

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

Le scénario:

#!/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
la source
4

Un moyen facile est d'utiliser colordiff, qui se comporte comme diffmais colorise sa sortie. Ceci est très utile pour lire les diffs. En utilisant votre exemple,

$ 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

où l' uoption donne un diff unifié. Voici à quoi ressemble le diff colorisé:

entrez la description de l'image ici

Installez colordiffen cours d'exécution sudo apt-get install colordiff.

edwinksl
la source
1
Si vous voulez des couleurs, je trouve le diff intégré à vim facile à utiliser, comme dans la réponse de M.S
thomasrutter
2

Réponse supplémentaire

S'il n'est pas nécessaire de savoir quelles parties des fichiers diffèrent, vous pouvez utiliser la somme de contrôle du fichier. Il y a plusieurs façons de le faire, en utilisant md5sumou sha256sum. Fondamentalement, chacun d’eux génère une chaîne contenant le hachage du fichier. Si les deux fichiers sont identiques, leur hachage sera également identique. Ceci est souvent utilisé lorsque vous téléchargez un logiciel, tel que des images iso d'installation Ubuntu. Ils sont souvent utilisés pour vérifier l'intégrité d'un contenu téléchargé.

Considérons le script ci-dessous, où vous pouvez donner deux fichiers en arguments, et le fichier vous dira s’ils sont identiques ou non.

#!/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

Échantillon échantillon:

$ ./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

Réponse plus ancienne

En outre, il existe une commcommande qui compare deux fichiers triés et génère une sortie en 3 colonnes: la colonne 1 pour les éléments uniques au fichier n ° 1, la colonne 2 pour les éléments uniques au fichier n ° 2 et la colonne 3 pour les éléments présents dans les deux fichiers.

Pour supprimer l'une ou l'autre colonne, vous pouvez utiliser les commutateurs -1, -2 et -3. Utiliser -3 montrera les lignes qui diffèrent.

Ci-dessous, vous pouvez voir la capture d'écran de la commande en action.

entrez la description de l'image ici

Une seule condition est requise: les fichiers doivent être triés pour pouvoir être comparés correctement. sortLa commande peut être utilisée à cette fin. Ci-dessous, une autre capture d'écran, où les fichiers sont triés puis comparés. Les lignes commençant à gauche près de File_1 seulement, les lignes commençant à la colonne 2 appartiennent à File_2 uniquement

entrez la description de l'image ici

Sergiy Kolodyazhnyy
la source
@DavidFoerster c'est un peu difficile à modifier sur mobile :) C'est fait maintenant
Sergiy Kolodyazhnyy
2

Installer git et utiliser

$ git diff filename1 filename2

Et vous obtiendrez une sortie dans un joli format coloré

Installation de Git

$ apt-get update
$ apt-get install git-core
Eric Korolev
la source
2

colcmp.sh

Compare les paires nom / valeur dans 2 fichiers au format name value\n. Écrit le nameà Output_filesi changé. Nécessite bash v4 + pour les tableaux associatifs .

Usage

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

Fichier de sortie

$ cat Output_File
User3 has changed

Source (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

Explication

Décomposition du code et de sa signification, au mieux de ma compréhension. Je me félicite des modifications et des suggestions.

Comparaison de fichier de base

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

cmp définira la valeur de $? comme suit :

  • 0 = fichiers correspondants
  • 1 = les fichiers diffèrent
  • 2 = erreur

J'ai choisi d'utiliser une instruction case .. esac to evalute $? parce que la valeur de $? change après chaque commande, y compris test ([).

Sinon, j'aurais pu utiliser une variable pour conserver la valeur de $? :

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

Ci-dessus fait la même chose que la déclaration de cas. IDK que j'aime mieux.

Effacer la sortie

        echo "" > Output_File

L'option ci-dessus efface le fichier de sortie. Par conséquent, si aucun utilisateur ne change, le fichier de sortie sera vide.

Je le fais dans les instructions case afin que le fichier de sortie reste inchangé en cas d'erreur.

Copier le fichier utilisateur dans un script shell

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

Ci-dessus copie File_1.txt dans le répertoire de départ de l'utilisateur actuel.

Par exemple, si l'utilisateur actuel est john, la procédure ci-dessus serait identique à cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh.

Échapper à des caractères spéciaux

En gros, je suis paranoïaque. Je sais que ces caractères peuvent avoir une signification particulière ou exécuter un programme externe lorsqu'ils sont exécutés dans un script dans le cadre d'une affectation de variable:

  • `- back-tick - exécute un programme et la sortie comme si la sortie faisait partie de votre script
  • $ - dollar sign - préfixe généralement une variable
  • $ {} - permet une substitution de variable plus complexe
  • $ () - idk ce que cela fait mais je pense qu'il peut exécuter du code

Ce que je ne sais pas, c'est combien je ne sais pas à propos de bash. Je ne sais pas quels autres personnages pourraient avoir une signification particulière, mais je veux leur échapper avec une barre oblique inverse:

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

sed peut faire beaucoup plus que la correspondance de motif d'expression régulière . Le modèle de script "s / (trouver) / (remplacer) /" effectue spécifiquement la correspondance de modèle.

"s / (trouver) / (remplacer) / (modificateurs)"

en anglais: saisir toute ponctuation ou caractère spécial en tant que groupe de caputures 1 (\\ 1)

  • (remplacez) = \\\\\\ 1
    • \\\\ = caractère littéral (\\) c'est-à-dire une barre oblique inverse
    • \\ 1 = groupe de capture 1

en anglais: préfixe tous les caractères spéciaux avec une barre oblique inverse

  • (modificateurs) = g
    • g = remplacer globalement

en anglais: si plus d'une correspondance est trouvée sur la même ligne, remplacez-les toutes

Commentez tout le script

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

Ci-dessus utilise une expression régulière pour préfixer chaque ligne de ~ / .colcmp.arrays.tmp.sh avec un caractère de commentaire bash ( # ). Je le fais parce que plus tard, j'ai l'intention d'exécuter ~ / .colcmp.arrays.tmp.sh en utilisant la commande source et parce que je ne sais pas avec certitude le format entier de File_1.txt .

Je ne veux pas exécuter accidentellement du code arbitraire. Je pense que personne ne le fait.

"s / (trouver) / (remplacer) /"

en anglais: capturez chaque ligne en tant que groupe de capacités 1 (\\ 1)

  • (remplacez) = # \\ 1
    • # = caractère littéral (#), c.-à-d. un symbole dièse ou hachage
    • \\ 1 = groupe de capture 1

en anglais: remplacez chaque ligne par un symbole dièse suivi de la ligne remplacée

Convertir une valeur utilisateur en A1 [User] = "valeur"

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

Ci-dessus se trouve le noyau de ce script.

  • Convertir cela: #User1 US
    • pour ça: A1[User1]="US"
    • ou ceci: A2[User1]="US"(pour le 2ème fichier)

"s / (trouver) / (remplacer) /"

en anglais:

  • nécessite mais ignore les caractères de commentaire (#)
  • ignorer les grands espaces
  • capturer le premier mot en tant que groupe de capacités 1 (\\ 1)
  • nécessite un espace (ou une tabulation, ou des espaces)
    • qui sera remplacé par un signe égal parce que
    • cela ne fait partie d'aucun groupe de capture, et parce que
    • le motif (remplacer) place un signe égal entre le groupe de capture 1 et le groupe de capture 2
  • capturer le reste de la ligne en tant que groupe de capture 2

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

    • A1 \\ [- caractères littéraux A1[pour commencer l’affectation de tableau dans un tableau appeléA1
    • \\ 1 = groupe de capture 1 - qui n'inclut pas le hachage (#) ni les espaces blancs - dans ce cas, le groupe de capture 1 est utilisé pour définir le nom de la paire nom / valeur dans le tableau associatif bash.
    • \\] = \ "= caractères littéraux ]="
      • ]= fermer l'assignation de tableau, par exemple, A1[User1 ]="US"
      • = = opérateur d'assignation, par exemple variable = valeur
      • " = valeur de citation pour capturer des espaces ... bien que, maintenant que j'y réfléchisse, il aurait été plus facile de laisser le code ci-dessus, qui supprime toutes les barres obliques inverses, ainsi que les espaces.
    • \\ 1 = groupe de capture 2 - dans ce cas, la valeur de la paire nom / valeur
    • "= valeur de devis de fermeture pour capturer des espaces

en anglais: remplacez chaque ligne du format #name valuepar un opérateur d'affectation de tableau au formatA1[name]="value"

Rendre exécutable

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

Ci-dessus utilise chmod pour rendre le fichier de script de tableau exécutable.

Je ne sais pas si c'est nécessaire.

Déclarer un tableau associatif (bash v4 +)

        declare -A A1

Le capital -A indique que les variables déclarées seront des tableaux associatifs .

C'est pourquoi le script nécessite bash version 4 ou supérieure.

Exécuter notre script d'assignation de variable de tableau

        source ~/.colcmp.arrays.tmp.sh

Nous avons déjà:

  • converti notre fichier de lignes User valueen lignes de A1[User]="value",
  • l'a rendu exécutable (peut-être), et
  • a déclaré A1 comme un tableau associatif ...

Au- dessus de nous la source du script pour l'exécuter dans le shell courant. Nous faisons cela afin de pouvoir conserver les valeurs de variables définies par le script. Si vous exécutez le script directement, il génère un nouveau shell et les valeurs des variables sont perdues lorsque le nouveau shell se ferme, ou du moins c'est ce que je comprends.

Cela devrait être une fonction

        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

Nous faisons la même chose pour 1 $ et A1 que pour 2 $ et A2 . Cela devrait vraiment être une fonction. Je pense qu'à ce stade, ce script est assez déroutant et qu'il fonctionne, donc je ne vais pas le réparer.

Détecter les utilisateurs supprimés

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

Boucles ci-dessus à travers des clés de tableau associatif

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

La méthode décrite ci-dessus utilise la substitution de variable pour détecter la différence entre une valeur non définie et une variable explicitement définie sur une chaîne de longueur nulle.

Apparemment, il y a beaucoup de façons de voir si une variable a été définie . J'ai choisi celui qui a reçu le plus de votes.

                echo "$i has changed" > Output_File

Ci-dessus ajoute l'utilisateur $ i au fichier de sortie

Détecter les utilisateurs ajoutés ou modifiés

        USERSWHODIDNOTCHANGE=

Ci-dessus efface une variable pour que nous puissions garder une trace des utilisateurs qui n'ont pas changé.

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

Boucles ci-dessus à travers des clés de tableau associatif

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

Ci-dessus utilise la substitution de variable pour voir si une variable a été définie .

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

Comme $ i est la clé du tableau (nom d'utilisateur), $ A2 [$ i] doit renvoyer la valeur associée à l'utilisateur actuel à partir de File_2.txt .

Par exemple, si $ i est Utilisateur1 , ce qui précède se lit comme suit : {{A2 [User1]}]

                echo "$i has changed" > Output_File

Ci-dessus ajoute l'utilisateur $ i au fichier de sortie

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

Parce que $ i est la clé du tableau (nom d'utilisateur), $ A1 [$ i] doit renvoyer la valeur associée à l'utilisateur actuel à partir de File_1.txt et $ A2 [$ i] doit renvoyer la valeur à partir de File_2.txt .

Ci-dessus compare les valeurs associées pour l'utilisateur $ i à partir des deux fichiers.

                echo "$i has changed" > Output_File

Ci-dessus ajoute l'utilisateur $ i au fichier de sortie

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

Ci-dessus crée une liste d'utilisateurs séparés par des virgules qui n'ont pas changé. Notez qu'il n'y a pas d'espaces dans la liste, sinon la coche suivante devra être citée.

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

Ci-dessus indique la valeur de $ USERSWHODIDNOTCHANGE, mais uniquement s'il existe une valeur dans $ USERSWHODIDNOTCHANGE . De la manière dont cela est écrit, $ USERSWHODIDNOTCHANGE ne peut contenir aucun espace. S'il y a un besoin d'espaces, les éléments ci-dessus pourraient être réécrits comme suit:

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