133 votes

Comment comparer deux fichiers

En gros, ce que je veux faire, c'est comparer deux fichiers par ligne et par colonne 2. Comment pourrais-je y parvenir ?

Fichier_1.txt :

User1 US
User2 US
User3 US

Fichier_2.txt :

User1 US
User2 US
User3 NG

Output_File :

User3 has changed

8voto

B Faley Points 484

Meld est un outil vraiment formidable. Mais vous pouvez aussi utiliser diffuse pour comparer visuellement deux fichiers :

diffuse file1.txt file2.txt

enter image description here

7voto

Jacob Vlijm Points 78990

En s'en tenant littéralement à la question (fichier1, fichier2, fichier de sortie avec message "a changé") le script ci-dessous fonctionne.

Copiez le script dans un fichier vide, enregistrez-le sous le nom de compare.py pour le rendre exécutable, exécutez-le par la commande :

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

Le script :

#!/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 faire en sorte qu'il s'imprime soit dans un fichier de sortie, soit dans le terminal, selon que le fichier de sortie est défini ou non :

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 script :

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

4voto

edwinksl Points 22609

Un moyen simple est d'utiliser colordiff qui se comporte comme diff mais colore sa sortie. C'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ù le u donne un diff unifié. Voici à quoi ressemble le diff colorisé :

enter image description here

Installer colordiff en courant sudo apt-get install colordiff .

3voto

Eric Korolev Points 285

Installer git et utiliser

$ git diff filename1 filename2

Et vous obtiendrez une sortie dans un format coloré agréable.

Git installation

$ apt-get update
$ apt-get install git-core

3voto

Jonathan Points 133

Colcmp.sh

Compare les paires nom/valeur dans 2 fichiers au format name value\n . Rédige le name à Output_file si elle a été modifiée. Nécessite bash v4+ pour tableaux associatifs .

Utilisation

$ ./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. Les modifications et les suggestions sont les bienvenues.

Comparaison de fichiers de base

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

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

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

J'ai choisi d'utiliser un cas .. esac pour évaluer l'instruction $ ? parce que la valeur de $ ? change après chaque commande, y compris test ([).

Alternativement, 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 l'instruction case. Je ne sais pas ce que je préfère.

Effacer la sortie

        echo "" > Output_File

Ce qui précède efface le fichier de sortie de sorte que si aucun utilisateur n'a changé, le fichier de sortie sera vide.

Je fais cela à l'intérieur de la cas afin que les Fichier de sortie reste inchangé en cas d'erreur.

Copier le fichier utilisateur dans Shell Shell

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

Copies ci-dessus Fichier_1.txt dans le répertoire personnel de l'utilisateur actuel.

Par exemple, si l'utilisateur actuel est john, la commande ci-dessus sera identique à cp "Fichier_1.txt" /home/john/.colcmp.arrays.tmp.sh

Échapper aux caractères spéciaux

En gros, je suis paranoïaque. Je sais que ces caractères pourraient avoir une signification spéciale 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.
  • $ - signe de dollar - préfixe généralement une variable
  • ${} - permet une substitution de variable plus complexe
  • $() - je ne sais pas ce qu'il fait mais je pense qu'il peut exécuter du code.

Ce que je je ne sais pas c'est à quel point je ne connais pas bash. Je ne sais pas quels autres caractères peuvent avoir une signification spéciale, mais je veux tous les échapper avec un backslash :

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

sed peut faire beaucoup plus que correspondance de motifs par expression régulière . Le modèle script "s/(find)/(replace)/" effectue spécifiquement la correspondance du motif.

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

en anglais : saisir toute ponctuation ou caractère spécial comme groupe de caputure 1 ( \\1 )

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

en anglais : préfixer tous les caractères spéciaux par 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 l'intégralité du 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 fais cela parce que plus tard j'ai l'intention d'exécuter ~/.colcmp.arrays.tmp.sh en utilisant le source et parce que je ne connais pas avec certitude le format complet de la Fichier_1.txt .

Je ne veux pas exécuter accidentellement un code arbitraire. Je ne pense pas que quiconque le veuille.

"s/(find)/(replace)/"

en anglais : capturer chaque ligne comme groupe de caputure 1 ( \\1 )

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

en anglais : remplacer chaque ligne par un symbole de dièse suivi de la ligne qui a été remplacée

Convertir la valeur de l'utilisateur en A1[Utilisateur]="valeur".

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

Ci-dessus se trouve le cœur de ce script.

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

"s/(find)/(replace)/"

en anglais :

  • requiert mais ignore les premiers caractères de commentaire (#)

  • ignore les espaces blancs de tête

  • capturer le premier mot comme groupe de caputure 1 ( \\1 )

  • nécessitent un espace (ou une tabulation, ou un espace blanc)

    • qui sera remplacé par un signe égal car
    • il ne fait partie d'aucun groupe de capture, et parce que
    • le motif (replace) met un signe égal entre le groupe de capture 1 et le groupe de capture 2
  • capturer le reste de la ligne comme groupe de capture 2

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

    • A1 \\ [ - caractères littéraux A1[ pour commencer l'affectation du tableau dans un tableau appelé A1
    • \\1 = groupe de capture 1 - qui n'inclut pas le dièse de tête (#) et n'inclut pas l'espace blanc de tête - dans ce cas, le groupe de capture 1 est utilisé pour définir le nom de la paire nom/valeur dans le tableau associatif de bash.
    • \\ ]=\" = caractères littéraux ]="
      • ] = fermer l'affectation de tableau, par exemple A1[ Utilisateur 1 ]=" US "
      • = = opérateur d'affectation, par exemple variable=valeur
      • " = valeur de citation pour capturer les espaces ... bien que, maintenant que j'y pense, il aurait été plus facile de laisser le code ci-dessus, qui met tout en arrière, mettre également en arrière les caractères d'espace.
    • \\1 = groupe de capture 2 - dans ce cas, la valeur de la paire nom/valeur
    • " = valeur du guillemet fermant pour capturer les espaces

en anglais : remplacer chaque ligne dans le format #name value avec un opérateur d'affectation de tableau au format A1[name]="value"

Rendre exécutable

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

Utilisations ci-dessus chmod pour rendre le fichier du tableau script exécutable.

Je ne suis pas sûr que ce soit nécessaire.

Déclarer un tableau associatif (bash v4+)

        declare -A A1

La majuscule -A indique que les variables déclarées seront tableaux associatifs .

C'est pourquoi le script nécessite bash v4 ou plus.

Exécuter notre assignation de variable de tableau script.

        source ~/.colcmp.arrays.tmp.sh

Nous l'avons déjà fait :

  • a converti notre fichier à partir de lignes de User value aux lignes de A1[User]="value" ,
  • le rendait exécutable (peut-être), et
  • a déclaré A1 comme un tableau associatif...

Au-dessus, nous source le script pour l'exécuter dans le script courant. Nous faisons cela pour pouvoir conserver les valeurs des variables qui sont définies par le script. Si vous exécutez le script directement, il génère une nouvelle script, et les valeurs des variables sont perdues lorsque la nouvelle script sort, 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 $1A1 que nous faisons pour $2A2 . Il devrait vraiment s'agir d'une fonction. Je pense qu'à ce stade, ce script est suffisamment confus et il fonctionne, donc je ne vais pas le corriger.

Détecter les utilisateurs supprimés

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

Les boucles ci-dessus passent par clés de tableaux associatifs

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

Above utilise la substitution de variable pour détecter la différence entre une valeur qui n'est pas définie et une variable qui a été explicitement définie comme 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 la trace des utilisateurs qui n'ont pas changé.

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

Les boucles ci-dessus passent par clés de tableaux associatifs

            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]}'"

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

Par exemple, si $i est Utilisateur 1 le texte ci-dessus se lit comme suit ${A2[Utilisateur1]}

                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 de l'utilisateur) $A1[$i] doit renvoyer la valeur associée à l'utilisateur actuel à partir de Fichier_1.txt et $A2[$i] devrait retourner la valeur de Fichier_2.txt .

Le tableau ci-dessus compare les valeurs associées pour l'utilisateur $i 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 séparée par des virgules des utilisateurs qui n'ont pas changé. Notez qu'il n'y a pas d'espace dans la liste, sinon la vérification suivante devrait être citée.

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

Le rapport ci-dessus indique la valeur de $USERSWHODIDNOTCHANGE mais seulement s'il existe une valeur dans $USERSWHODIDNOTCHANGE . La façon dont c'est écrit, $USERSWHODIDNOTCHANGE ne peut pas contenir d'espaces. S'il a besoin d'espaces, le texte ci-dessus pourrait être réécrit comme suit :

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

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X