web-dev-qa-db-fra.com

Comment comparer deux fichiers

Donc, en gros, ce que je veux faire, c'est 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
78
Roboman1723

Examinez la commande diff. C'est un bon outil, et vous pouvez tout lire à ce sujet en tapant man diff dans votre terminal.

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

enter image description here

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é en premier sur la ligne de commande, dans ce cas, File_1.txt

De plus, vous remarquerez peut-être que la 4ème commande est diff ... | tee Output_File, ce qui dirige les résultats de diff dans un tee, qui place ensuite cette sortie dans un fichier, afin que vous puissiez l'enregistrer pour plus tard si vous ne voulez pas tout afficher à droite. cette seconde.

86
Mitch

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:

enter image description here

Comparer le répertoire:

enter image description here

Exemple avec plein de texte:

enter image description here

36
Achu

Vous pouvez utiliser vimdiff .

Exemple:

vimdiff  file1  file2
18
Mr. S

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
10
Mike Reardon

Meld est un très bon outil. Mais vous pouvez également utiliser diffuse pour comparer visuellement deux fichiers:

diffuse file1.txt file2.txt

enter image description here

8
Meysam

Vous pouvez utiliser la commande cmpNAME _ :

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

la sortie serait

a b differ: byte 25, line 3 is 125 U 116 N
8
Maythux

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"
7
Jacob Vlijm

Un moyen simple consiste à utiliser colordiffname__, 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'option udonne un diff unifié. Voici à quoi ressemble le diff colorisé:

enter image description here

Installez colordiffen exécutant Sudo apt-get install colordiff.

4
edwinksl

Installer git et utiliser

$ git diff filename1 filename2

Et vous obtiendrez une sortie au format Nice coloré

Git installation

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

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 md5sum ou 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

De plus, la commande comm compare deux fichiers triés et génère une sortie en 3 colonnes: la colonne 1 pour les éléments uniques du fichier n ° 1, la colonne 2 pour les éléments uniques du 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.

enter image description here

Une seule condition est requise: les fichiers doivent être triés pour pouvoir être comparés correctement. La commande sort 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

enter image description here

2

colcmp.sh

Compare les paires nom/valeur dans 2 fichiers au format name value\n. Écrit le name à Output_file s’il a été modifié. Nécessite bash v4 + pour 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 case .. esac == instruction à évaluer $ ? parce que la valeur de $? == change après chaque commande, y compris test ) = = ([).

Sinon, j'aurais pu utiliser une variable pour contenir 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. Ainsi, si aucun utilisateur ne change, le fichier de sortie sera vide.

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

Copier le fichier utilisateur dans un script shell

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

Copies ci-dessus File_1.txt dans le répertoire personnel 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 correspondance du modèle 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: capturez toute ponctuation ou caractère spécial en tant que groupe de fonctions 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 tous

Commenter le script entier

        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 je compte exécuter ~/.colcmp.arrays.tmp.sh en utilisant la commande source et parce que je 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: capturer chaque ligne en tant que groupe de fonctions 1 (\\ 1)

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

en anglais: remplace 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 est le noyau de ce script.

  • convertir ceci: #User1 US
    • à ceci: A1[User1]="US"
    • ou ceci: A2[User1]="US" (pour le 2e 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 pas partie d'un 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 du nom/valeur paire 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 quote 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 au format #name value par un opérateur d'assignation de tableau au format A1[name]="value"

Rendre exécutable

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

Ci-dessus, utilisez 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 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 de User value en lignes de A1[User]="value",
  • l'a rendu exécutable (peut-être), et
  • a déclaré A1 comme un tableau associatif ...

Ci-dessus nous source ) == le script pour l'exécuter dans le shell actuel. Nous faisons cela afin de pouvoir conserver les valeurs de variables définies par le script. Si vous exécutez le script directement, il crée 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 nous faisons 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 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 Output_File

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 clés de tableau associatif

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

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

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

Parce que $ 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 , la valeur ci-dessus est la suivante: $ {A2 [Utilisateur1]}

                echo "$i has changed" > Output_File

Ci-dessus ajoute l’utilisateur $ i au Output_File

            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] devrait renvoyer la valeur de File_2.txt .

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

                echo "$i has changed" > Output_File

Ci-dessus ajoute l’utilisateur $ i au Output_File

                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 $ USERSWHODIDNOTCHANGE mais uniquement s'il existe une valeur $ USERSWHODIDNOTCHANGE . La manière dont ceci est écrit, $ USERSWHODIDNOTCHANGE ne peut contenir aucun espace. S'il y a un besoin d'espaces, les éléments ci-dessus pourraient être récrit comme suit

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