it-swarm.com.de

So vergleichen Sie zwei Dateien

Im Grunde möchte ich also zwei Dateien zeilenweise in Spalte 2 vergleichen. Wie könnte ich das erreichen?

Datei_1.txt:

User1 US
User2 US
User3 US

Datei_2.txt:

User1 US
User2 US
User3 NG

Ausgabedatei:

User3 has changed
82
Roboman1723

Sehen Sie sich den Befehl diff an. Es ist ein gutes Werkzeug und Sie können alles darüber lesen, indem Sie man diff in Ihr Terminal eingeben.

Der Befehl, den Sie ausführen möchten, ist diff File_1.txt File_2.txt, der den Unterschied zwischen den beiden ausgibt und ungefähr so ​​aussehen sollte:

enter image description here

Ein kurzer Hinweis zum Lesen der Ausgabe des dritten Befehls: Die 'Pfeile' (< und >) beziehen sich auf den Wert der Zeile in der linken Datei (<) vs die rechte Datei (>), wobei die linke Datei diejenige ist, die Sie zuerst in der Befehlszeile eingegeben haben, in diesem Fall File_1.txt

Außerdem stellen Sie möglicherweise fest, dass der vierte Befehl diff ... | tee Output_File ist. Dadurch werden die Ergebnisse von diff in ein tee umgeleitet, das diese Ausgabe dann in eine Datei speichert, damit Sie sie später speichern können Ich möchte nicht, dass in dieser Sekunde alles auf der Konsole angezeigt wird.

90
Mitch

Oder Sie können Meld Diff verwenden

Mit Meld können Sie Dateien, Verzeichnisse und versionskontrollierte Projekte vergleichen. Es bietet einen Zwei- und Drei-Wege-Vergleich von Dateien und Verzeichnissen und unterstützt viele gängige Versionskontrollsysteme.

Installieren Sie, indem Sie Folgendes ausführen:

Sudo apt-get install meld

Ihr Beispiel:

enter image description here

Verzeichnis vergleichen:

enter image description here

Beispiel mit vollem Text:

enter image description here

36
Achu

Sie können vimdiff verwenden.

Beispiel:

vimdiff  file1  file2
18
Mr. S

FWIW, ich mag eher, was ich mit Side-by-Side-Ausgabe von diff bekomme

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

würde etwas geben wie:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG
12
Mike Reardon

Sie können den Befehl cmp verwenden:

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

ausgabe wäre

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

Meld ist ein wirklich tolles Werkzeug. Sie können jedoch auch diffuse verwenden, um zwei Dateien visuell zu vergleichen:

diffuse file1.txt file2.txt

enter image description here

8
Meysam

Wenn Sie sich an die Frage halten (Datei1, Datei2, Ausgabedatei mit der Meldung "Hat sich geändert"), funktioniert das folgende Skript.

Kopieren Sie das Skript in eine leere Datei, speichern Sie es als compare.py, machen Sie es ausführbar und führen Sie es mit dem folgenden Befehl aus:

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

Das Drehbuch:

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

Mit ein paar zusätzlichen Zeilen können Sie es entweder in eine Ausgabedatei oder auf das Terminal drucken lassen, je nachdem, ob die Ausgabedatei definiert ist:

So drucken Sie in eine Datei:

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

So drucken Sie in das Terminalfenster:

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

Das Drehbuch:

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

Eine einfache Möglichkeit ist die Verwendung von colordiff, das sich wie diff verhält, jedoch die Ausgabe einfärbt. Dies ist sehr hilfreich zum Lesen von Unterschieden. Verwenden Sie Ihr Beispiel,

$ 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

wobei die Option u einen einheitlichen Unterschied ergibt. So sieht das eingefärbte Diff aus:

enter image description here

Installieren Sie colordiff, indem Sie Sudo apt-get install colordiff ausführen.

4
edwinksl

Installiere git und benutze

$ git diff filename1 filename2

Und Sie erhalten eine Ausgabe in netten Farben

Git Installation

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

Zusätzliche Antwort

Wenn Sie nicht wissen müssen, welche Teile der Dateien unterschiedlich sind, können Sie die Prüfsumme der Datei verwenden. Es gibt viele Möglichkeiten, dies mit md5sum oder sha256sum zu tun. Grundsätzlich gibt jeder von ihnen eine Zeichenfolge aus, zu der ein Dateiinhalt-Hash gehört. Wenn die beiden Dateien identisch sind, ist auch der Hash identisch. Dies wird häufig verwendet, wenn Sie Software herunterladen, z. B. Ubuntu-Installations-ISO-Images. Sie werden häufig zur Überprüfung der Integrität eines heruntergeladenen Inhalts verwendet.

Betrachten Sie das folgende Skript, in dem Sie zwei Dateien als Argumente angeben können. In der Datei wird angegeben, ob sie identisch sind oder nicht.

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

Probelauf:

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

Ältere Antwort

Darüber hinaus gibt es den Befehl comm, der zwei sortierte Dateien vergleicht und die Ausgabe in drei Spalten ausgibt: Spalte 1 für Elemente, die für Datei 1 eindeutig sind, Spalte 2 für Elemente, die für Datei 2 eindeutig sind, und Spalte 3 für Elemente, die vorhanden sind in beiden Dateien.

Zum Unterdrücken einer Spalte können Sie die Schalter -1, -2 und -3 verwenden. Mit -3 werden die Zeilen angezeigt, die sich unterscheiden.

Unten sehen Sie den Screenshot des Befehls in Aktion.

enter image description here

Es gibt nur eine Anforderung: Die Dateien müssen sortiert sein, damit sie richtig verglichen werden können. Zu diesem Zweck kann der Befehl sort verwendet werden. Unten sehen Sie einen weiteren Screenshot, in dem Dateien sortiert und dann verglichen werden. Zeilen, die links beginnen, gehören nur zu File_1, Zeilen, die in Spalte 2 beginnen, gehören nur zu File_2

enter image description here

2

colcmp.sh

Vergleicht Name/Wert-Paare in 2 Dateien im Format name value\n. Schreibt das name nach Output_file, wenn es geändert wurde. Benötigt bash v4 + für assoziative Arrays .

Verwendungszweck

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

Ausgabedatei

$ cat Output_File
User3 has changed

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

Erläuterung

Aufschlüsselung des Codes und was er bedeutet, nach bestem Wissen. Ich freue mich über Änderungen und Vorschläge.

Basic File Compare

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

cmp setzt den Wert von $? auf folgt =:

  • 0 = Dateien stimmen überein
  • 1 = Dateien unterscheiden sich
  • 2 = Fehler

Ich habe mich für die Verwendung einer case .. esac -Anweisung entschieden, um $? auszuwerten ) weil der Wert von $? sich nach jedem Befehl ändert, einschließlich test ([).

Alternativ hätte ich eine Variable verwenden können, um den Wert von $? zu speichern:

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

Oben wird dasselbe wie in der case-Anweisung gemacht. IDK was mir besser gefällt.

Löschen Sie die Ausgabe

        echo "" > Output_File

Oben wird die Ausgabedatei gelöscht. Wenn also keine Benutzer geändert wurden, ist die Ausgabedatei leer.

Ich mache dies in den case Anweisungen, damit die Output_file im Fehlerfall unverändert bleibt.

Kopieren Sie die Benutzerdatei in das Shell-Skript

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

Oben kopiert File_1.txt in das Ausgangsverzeichnis des aktuellen Benutzers.

Wenn der aktuelle Benutzer beispielsweise john ist, entspricht das oben Genannte cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Sonderzeichen entkommen

Grundsätzlich bin ich paranoid. Ich weiß, dass diese Zeichen eine besondere Bedeutung haben oder ein externes Programm ausführen können, wenn sie in einem Skript als Teil der Variablenzuweisung ausgeführt werden:

  • `- back-tick - führt ein Programm und die Ausgabe so aus, als ob die Ausgabe Teil Ihres Skripts wäre
  • $ - Dollarzeichen - stellt normalerweise eine Variable voran
  • $ {} - ermöglicht eine komplexere Variablensubstitution
  • $ () - idk was dies tut, aber ich denke, es kann Code ausführen

Was ich nicht weiß , ist, wie viel ich nicht über Bash weiß. Ich weiß nicht, welche anderen Zeichen eine besondere Bedeutung haben könnten, aber ich möchte sie alle mit einem Backslash umgehen:

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

sed kann viel mehr als Mustervergleich für reguläre Ausdrücke . Das Skriptmuster "s/(find)/(replace) /" führt speziell die Musterübereinstimmung durch.

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

in Englisch: Zeichensetzung oder Sonderzeichen als Erfassungsgruppe 1 erfassen (\\ 1)

  • (Ersetzen) = \\\\\\ 1

in Englisch: stellen Sie allen Sonderzeichen einen Backslash voran

  • (Modifikatoren) = g
    • g = global ersetzen

in Englisch: Wenn mehr als eine Übereinstimmung in derselben Zeile gefunden wird, ersetzen Sie sie alle

Kommentieren Sie das gesamte Skript aus

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

Oben wird ein regulärer Ausdruck verwendet, um jeder Zeile von ~/.colcmp.arrays.tmp.sh ein Bash-Kommentarzeichen ( # ) voranzustellen. Ich mache das, weil ich später ~/.colcmp.arrays.tmp.sh mit dem source Befehl ausführen möchte und weil ich es nicht weiß mit Sicherheit das gesamte Format von File_1.txt .

Ich möchte nicht versehentlich beliebigen Code ausführen. Ich glaube nicht, dass jemand das tut.

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

in Englisch: Erfassen Sie jede Zeile als Erfassungsgruppe 1 (\\ 1)

  • (Ersetzen) = # \\ 1
    • # = Literales Zeichen (#), d. h. ein Rautezeichen oder ein Rautezeichen
    • \\ 1 = Erfassungsgruppe 1

in Englisch: Ersetzen Sie jede Zeile durch ein Pfund-Symbol, gefolgt von der Zeile, die ersetzt wurde

Benutzerwert in A1 konvertieren [User] = "value"

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

Oben ist der Kern dieses Skripts.

  • konvertiere dies: #User1 US
    • dazu: A1[User1]="US"
    • oder dies: A2[User1]="US" (für die 2. Datei)

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

auf Englisch:

  • benötige aber ignoriere führende Kommentarzeichen (#)
  • ignorieren Sie führende Leerzeichen
  • erfassen Sie das erste Wort als Erfassungsgruppe 1 (\\ 1)
  • leerzeichen (oder Tabulator oder Leerzeichen) benötigen
    • das wird durch ein Gleichheitszeichen ersetzt, weil
    • es ist nicht Teil einer Erfassungsgruppe, und weil
    • das Muster (Ersetzen) setzt ein Gleichheitszeichen zwischen Erfassungsgruppe 1 und Erfassungsgruppe 2
  • erfassen Sie den Rest der Zeile als Erfassungsgruppe 2

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

    • A1 \\ [- Literalzeichen A1[ zum Starten der Arrayzuweisung in einem Array mit dem Namen A1
    • \\ 1 = Erfassungsgruppe 1 - ohne führenden Hash (#) und ohne führenden Whitespace - in diesem Fall wird Erfassungsgruppe 1 verwendet, um den Namen des Namens/Werts festzulegen Paar im Bash-Assoziativ-Array.
    • \\] =\"= Literalzeichen ]="
      • ] = nahe Arrayzuordnung, z. A1[User1]="US"
      • = = Zuweisungsoperator, z. Variable = Wert
      • " = Anführungszeichen zum Erfassen von Leerzeichen ... obwohl es jetzt, wo ich darüber nachdenke, einfacher gewesen wäre, den Code über diesem Backslash alles in Backslash-Leerzeichen umzuwandeln.
    • \\ 1 = Erfassungsgruppe 2 - in diesem Fall der Wert des Name/Wert-Paares
    • "= schließender Anführungszeichenwert zum Erfassen von Leerzeichen

in Englisch: Ersetzen Sie jede Zeile im Format #name value durch einen Array-Zuweisungsoperator im Format A1[name]="value"

Ausführbar machen

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

Oben wird chmod verwendet, um die Array-Skriptdatei ausführbar zu machen.

Ich bin mir nicht sicher, ob das notwendig ist.

Assoziatives Array deklarieren (bash v4 +)

        declare -A A1

Das Großbuchstaben -A gibt an, dass die deklarierten Variablen assoziative Arrays sind.

Aus diesem Grund benötigt das Skript bash v4 oder höher.

Führen Sie unser Array-Variablenzuweisungsskript aus

        source ~/.colcmp.arrays.tmp.sh

Wir haben schon:

  • konvertierte unsere Datei von User value in A1[User]="value",
  • machte es ausführbar (vielleicht), und
  • deklarierte A1 als assoziatives Array ...

Oben source das Skript, um es in der aktuellen Shell auszuführen. Wir tun dies, um die vom Skript gesetzten Variablenwerte beizubehalten. Wenn Sie das Skript direkt ausführen, wird eine neue Shell erstellt, und die Variablenwerte gehen verloren, wenn die neue Shell beendet wird.

Dies sollte eine Funktion sein

        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

Wir machen dasselbe für $ 1 und A1 wie für $ 2 und A2 . Es sollte wirklich eine Funktion sein. Ich denke, an diesem Punkt ist dieses Skript verwirrend genug und es funktioniert, also werde ich es nicht reparieren.

Benutzer erkennen, die entfernt wurden

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

Oben durchläuft assoziative Array-Tasten

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

Oben wird die Variablensubstitution verwendet, um den Unterschied zwischen einem nicht gesetzten Wert und einer Variablen zu ermitteln, die explizit auf eine Zeichenfolge mit der Länge Null festgelegt wurde.

Anscheinend gibt es viele Möglichkeiten, um nachzusehen, ob eine Variable gesetzt wurde . Ich habe den mit den meisten Stimmen gewählt.

                echo "$i has changed" > Output_File

Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt

Erkennen Sie hinzugefügte oder geänderte Benutzer

        USERSWHODIDNOTCHANGE=

Oben wird eine Variable gelöscht, damit wir die Benutzer verfolgen können, die sich nicht geändert haben.

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

Oben durchläuft assoziative Array-Tasten

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

Oben wird die Variablensubstitution für nachsehen, ob eine Variable gesetzt wurde verwendet.

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

Da $ i der Array-Schlüssel (Benutzername) ist, sollte $ A2 [$ i] den dem aktuellen Benutzer zugeordneten Wert aus File_2.txt zurückgeben. .

Wenn zum Beispiel $ i User1 ist, lautet der obige Wert $ {A2 [User1]}

                echo "$i has changed" > Output_File

Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt

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

Da $ i der Array-Schlüssel (Benutzername) ist, sollte $ A1 [$ i] den dem aktuellen Benutzer zugeordneten Wert aus File_1.txt zurückgeben. , und $ A2 [$ i] sollten den Wert von File_2.txt zurückgeben.

Oben werden die zugeordneten Werte für user $ i aus beiden Dateien verglichen.

                echo "$i has changed" > Output_File

Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt

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

Oben wird eine durch Kommas getrennte Liste von Benutzern erstellt, die sich nicht geändert haben. Beachten Sie, dass die Liste keine Leerzeichen enthält. Andernfalls muss der nächste Scheck in Anführungszeichen gesetzt werden.

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

Oben wird der Wert von $ USERSWHODIDNOTCHANGE angegeben, jedoch nur, wenn $ USERSWHODIDNOTCHANGE einen Wert enthält. So wie dies geschrieben ist, darf $ USERSWHODIDNOTCHANGE keine Leerzeichen enthalten. Wenn Leerzeichen erforderlich sind, könnte dies wie folgt geändert werden:

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