Vergleichen des Inhalts zweier Verzeichnisse


92

Ich habe zwei Verzeichnisse, die die gleichen Dateien enthalten sollten und die gleiche Verzeichnisstruktur haben.

Ich denke, dass in einem dieser Verzeichnisse etwas fehlt.

Gibt es eine Möglichkeit, mithilfe der Bash-Shell meine Verzeichnisse zu vergleichen und festzustellen, ob in einem der Verzeichnisse Dateien fehlen, die in dem anderen vorhanden sind?


1
Was ist die Ausgabe von bash --version?
jobin

Antworten:


63

Ein guter Weg, um diesen Vergleich durchzuführen, ist die Verwendung findvon md5sum, dann a diff.

Beispiel

Verwenden Sie find, um alle Dateien im Verzeichnis aufzulisten. Berechnen Sie dann den MD5-Hash für jede Datei und leiten Sie ihn sortiert nach Dateinamen in eine Datei um:

find /dir1/ -type f -exec md5sum {} + | sort -k 2 > dir1.txt

Gehen Sie in einem anderen Verzeichnis genauso vor:

find /dir2/ -type f -exec md5sum {} + | sort -k 2 > dir2.txt

Dann vergleiche das Ergebnis zweier Dateien mit diff:

diff -u dir1.txt dir2.txt

Oder als einzelner Befehl mit Prozessersetzung:

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2) <(find /dir2/ -type f -exec md5sum {} + | sort -k 2)

Wenn Sie nur die Änderungen sehen möchten:

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2 | cut -f1 -d" ") <(find /dir2/ -type f -exec md5sum {} + | sort -k 2 | cut -f1 -d" ")

Der Befehl cut gibt nur den Hash (erstes Feld) aus, der mit diff verglichen werden soll. Andernfalls gibt diff jede Zeile aus, da sich die Verzeichnispfade auch bei gleichem Hash unterscheiden.

Aber Sie werden nicht wissen, welche Datei geändert wurde ...

Dafür kann man sowas probieren

diff <(find /dir1/ -type f -exec md5sum {} + | sort -k 2 | sed 's/ .*\// /') <(find /dir2/ -type f -exec md5sum {} + | sort -k 2 | sed 's/ .*\// /')

Diese Strategie ist sehr nützlich, wenn sich die beiden zu vergleichenden Verzeichnisse nicht auf demselben Computer befinden und Sie sicherstellen müssen, dass die Dateien in beiden Verzeichnissen gleich sind.

Eine andere gute Möglichkeit, den Job zu erledigen, ist die Verwendung des diffBefehls von Git (kann Probleme verursachen, wenn Dateien unterschiedliche Berechtigungen haben -> dann wird jede Datei in der Ausgabe aufgelistet):

git diff --no-index dir1/ dir2/

1
Dies funktioniert nicht ohne einen zusätzlichen Sortierschritt, da die Reihenfolge, in der finddie Dateien aufgelistet werden, im Allgemeinen zwischen den beiden Verzeichnissen unterschiedlich ist.
Faheem Mitha

1
Sie können die Dateien nach der in askubuntu.com/a/662383/15729 beschriebenen Methode sortieren.
Faheem Mitha

1
Ich erhalte die Fehlermeldung `` find: md5sum: Keine solche Datei oder Verzeichnis
Houman

1
@Houman Ich weiß nicht, welche Linux-Distribution Sie verwenden, aber möglicherweise müssen Sie ein Paket installieren, das de md5sum bereitstellt. In Fedora 26 können Sie es installieren mit: #dnf install coreutils
Adail Junior

Verwenden Sie stattdessen md5 ()
boj

81

Sie können den diffBefehl genauso verwenden, wie Sie ihn für Dateien verwenden würden:

diff <directory1> <directory2>

Wenn Sie auch Unterordner und -dateien sehen möchten, können Sie die folgende -rOption verwenden:

diff -r <directory1> <directory2>

2
Wusste nicht, dass dies auch difffür Verzeichnisse funktioniert (man diff hat dies bestätigt), prüft jedoch nicht rekursiv, ob Änderungen in Unterverzeichnissen innerhalb von Unterverzeichnissen vorliegen.
jobin

1
@Jobin Das ist seltsam ... Für mich funktioniert es.
Alex R.

1
Ich habe so etwas wie diese: a/b/c/d/a, x/b/c/d/b. Sehen Sie, was diff a xSie gibt.
jobin

2
Sie müssen die -rOption verwenden. Das ( diff -r a x) gibt mir:Only in a/b/c/d: a. only in x/b/c/d: b.
Alex R.

3
diff zeige mir den Unterschied INTO-Dateien aber nicht, wenn ein Verzeichnis eine Datei enthält, die die andere nicht enthält !!! Ich muss die Unterschiede in der Datei nicht kennen, aber auch, ob eine Datei in einem Verzeichnis und nicht in dem anderen vorhanden ist
AndreaNobili

25

Wenn Sie bash nicht verwenden, können Sie diff mit --briefund verwenden --recursive:

$ diff -rq dir1 dir2 
Only in dir2: file2
Only in dir1: file1

Das man diffbeinhaltet beide Optionen:

-q, --brief
nur melden, wenn Dateien unterschiedlich sind

-r, --recursive
Vergleichen rekursiv alle Unterverzeichnisse gefunden


13

Hier ist eine Alternative, um nur Dateinamen und nicht deren Inhalt zu vergleichen:

diff <(cd folder1 && find . | sort) <(cd folder2 && find . | sort)

Dies ist eine einfache Möglichkeit, fehlende Dateien aufzulisten, aber es werden natürlich keine Dateien mit demselben Namen, aber unterschiedlichem Inhalt erkannt!

(Ich persönlich benutze mein eigenes diffdirsSkript, aber das ist Teil einer größeren Bibliothek .)


3
Sie sollten die Prozessersetzung verwenden, nicht temporäre Dateien ...
16.

3
Beachten Sie, dass dies keine Dateinamen mit bestimmten Sonderzeichen unterstützt. In diesem Fall möchten Sie möglicherweise Nulltrennzeichen verwenden, die von AFAIK derzeit diffnicht unterstützt werden. Aber es gibt commwelche, die es unterstützen, da git.savannah.gnu.org/cgit/coreutils.git/commit/…. Sobald es zu einem Coreutils in Ihrer Nähe kommt, können Sie dies tun comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(dessen Ausgabe Sie möglicherweise weiter in das Format konvertieren müssen) Sie müssen den --output-delimiterParameter und zusätzliche Werkzeuge verwenden).
Phk

7

Möglicherweise besteht eine Möglichkeit darin, rsync zweimal auszuführen:

rsync -r -n -t -v -O --progress -c -s /dir1/ /dir2/

In der vorherigen Zeile erhalten Sie Dateien, die sich in dir1 befinden und in dir2 unterschiedlich sind (oder fehlen).

rsync -r -n -t -v -O --progress -c -s /dir2/ /dir1/

Das selbe für dir2

#from the rsync --help :
-r, --recursive             recurse into directories
-n, --dry-run               perform a trial run with no changes made
-t, --times                 preserve modification times
-v, --verbose               increase verbosity
    --progress              show progress during transfer
-c, --checksum              skip based on checksum, not mod-time & size
-s, --protect-args          no space-splitting; only wildcard special-chars
-O, --omit-dir-times        omit directories from --times

Sie können die -nOption löschen , um die Änderungen zu übernehmen. Das kopiert die Liste der Dateien in den zweiten Ordner.

In diesem Fall ist es möglicherweise eine gute Option -u, neuere Dateien nicht zu überschreiben.

-u, --update                skip files that are newer on the receiver

Ein Einzeiler:

rsync -rtvcsOu -n --progress /dir1/ /dir2/ && rsync -rtvcsOu -n --progress /dir2/ /dir1/

3

Wenn Sie jede Datei erweiterbar und komprimierbar machen möchten, können Sie die Ausgabe von diff -rin Vim umleiten.

Zuerst geben wir Vim ein Zollstock:

mkdir -p ~/.vim/ftplugin
echo "set foldexpr=getline(v:lnum)=~'^diff.*'?'>1':1 foldmethod=expr fdc=2" >> ~/.vim/ftplugin/diff.vim

Jetzt gerade:

diff -r dir1 dir2 | vim -

Sie können schlagen zound zcFalten öffnen und schließen. Um Vim zu verlassen, drücke:q<Enter>


3

Ziemlich einfache Aufgabe in Python zu erreichen:

python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' DIR1 DIR2

Tatsächliche Werte für DIR1und ersetzen DIR2.

Hier ist ein Probelauf:

$ python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' Desktop/ Desktop
SAME
$ python -c 'import os,sys;d1=os.listdir(sys.argv[1]);d2=os.listdir(sys.argv[2]);d1.sort();d2.sort();x="SAME" if d1 == d2 else "DIFF";print x' Desktop/ Pictures/
DIFF

Zur besseren Lesbarkeit ist hier ein aktuelles Skript anstelle eines Einzeilers:

#!/usr/bin/env python
import os, sys

d1 = os.listdir(sys.argv[1])
d2 = os.listdir(sys.argv[2])
d1.sort()
d2.sort()

if d1 == d2:
    print("SAME")
else:
    print("DIFF")

2
Beachten Sie, dass der os.listdirkeine bestimmte Reihenfolge gibt. Die Listen könnten also die gleichen Dinge in unterschiedlicher Reihenfolge haben und der Vergleich würde fehlschlagen.
Muru

1
@muru guter Punkt, ich werde das Sortieren einschließen
Sergiy Kolodyazhnyy

3

Inspiriert von Sergijs Antwort habe ich mein eigenes Python-Skript geschrieben, um zwei Verzeichnisse zu vergleichen.

Im Gegensatz zu vielen anderen Lösungen werden die Inhalte der Dateien nicht verglichen. Es geht auch nicht in Unterverzeichnisse, die in einem der Verzeichnisse fehlen. Die Ausgabe ist also ziemlich kurz und das Skript arbeitet schnell mit großen Verzeichnissen.

#!/usr/bin/env python3

import os, sys

def compare_dirs(d1: "old directory name", d2: "new directory name"):
    def print_local(a, msg):
        print('DIR ' if a[2] else 'FILE', a[1], msg)
    # ensure validity
    for d in [d1,d2]:
        if not os.path.isdir(d):
            raise ValueError("not a directory: " + d)
    # get relative path
    l1 = [(x,os.path.join(d1,x)) for x in os.listdir(d1)]
    l2 = [(x,os.path.join(d2,x)) for x in os.listdir(d2)]
    # determine type: directory or file?
    l1 = sorted([(x,y,os.path.isdir(y)) for x,y in l1])
    l2 = sorted([(x,y,os.path.isdir(y)) for x,y in l2])
    i1 = i2 = 0
    common_dirs = []
    while i1<len(l1) and i2<len(l2):
        if l1[i1][0] == l2[i2][0]:      # same name
            if l1[i1][2] == l2[i2][2]:  # same type
                if l1[i1][2]:           # remember this folder for recursion
                    common_dirs.append((l1[i1][1], l2[i2][1]))
            else:
                print_local(l1[i1],'type changed')
            i1 += 1
            i2 += 1
        elif l1[i1][0]<l2[i2][0]:
            print_local(l1[i1],'removed')
            i1 += 1
        elif l1[i1][0]>l2[i2][0]:
            print_local(l2[i2],'added')
            i2 += 1
    while i1<len(l1):
        print_local(l1[i1],'removed')
        i1 += 1
    while i2<len(l2):
        print_local(l2[i2],'added')
        i2 += 1
    # compare subfolders recursively
    for sd1,sd2 in common_dirs:
        compare_dirs(sd1, sd2)

if __name__=="__main__":
    compare_dirs(sys.argv[1], sys.argv[2])

Wenn Sie es in einer Datei mit dem Namen speichern compare_dirs.py, können Sie es mit Python3.x ausführen:

python3 compare_dirs.py dir1 dir2

Beispielausgabe:

user@laptop:~$ python3 compare_dirs.py old/ new/
DIR  old/out/flavor-domino removed
DIR  new/out/flavor-maxim2 added
DIR  old/target/vendor/flavor-domino removed
DIR  new/target/vendor/flavor-maxim2 added
FILE old/tmp/.kconfig-flavor_domino removed
FILE new/tmp/.kconfig-flavor_maxim2 added
DIR  new/tools/tools/LiveSuit_For_Linux64 added

PS Wenn Sie Dateigrößen und Datei-Hashes auf mögliche Änderungen vergleichen müssen, habe ich hier ein aktualisiertes Skript veröffentlicht: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779


1
Danke, ich habe einen optionalen dritten Parameter regexp hinzugefügt , um gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684 zu überspringen / zu ignorieren , um genau das zu machen, was ich brauchte:cmpdirs dir1 dir2 '/\.git/'
Mike

0

Ich werde dieser Liste eine NodeJs-Alternative hinzufügen, die ich vor einiger Zeit geschrieben habe.

Dir-Vergleich

npm install dir-compare -g
dircompare dir1 dir2

0

Ich möchte ein großartiges Tool vorschlagen, das ich gerade entdeckt habe: MELD .

Es funktioniert einwandfrei und alles, was Sie mit dem Befehl diffauf einem Linux-basierten System tun können, kann dort mit einer schönen grafischen Oberfläche repliziert werden! Genießen

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.