Porównywanie zawartości dwóch katalogów

92

Mam dwa katalogi, które powinny zawierać te same pliki i tę samą strukturę katalogów.

Myślę, że czegoś brakuje w jednym z tych katalogów.

Czy korzystając z powłoki bash istnieje sposób na porównanie moich katalogów i sprawdzenie, czy w jednym z nich brakuje plików znajdujących się w drugim?

AndreaNobili
źródło
1
Jaka jest wydajność bash --version?
jobin
1
Podobne, ale bardziej szczegółowe: stackoverflow.com/questions/16787916/...
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

63

Dobrym sposobem na to porównanie jest użycie findz md5sum, a następnie a diff.

Przykład

Użyj polecenia find, aby wyświetlić listę wszystkich plików w katalogu, a następnie obliczyć skrót md5 dla każdego pliku i potokować go posortowanym według nazwy pliku do pliku:

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

Wykonaj tę samą procedurę w innym katalogu:

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

Następnie porównaj wynik dwóch plików z diff:

diff -u dir1.txt dir2.txt

Lub jako pojedyncze polecenie przy użyciu podstawienia procesu:

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

Jeśli chcesz zobaczyć tylko zmiany:

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" ")

Polecenie cięcia drukuje tylko skrót (pierwsze pole) do porównania przez diff. W przeciwnym razie diff wypisze każdą linię, ponieważ ścieżki katalogu różnią się, nawet jeśli skrót jest taki sam.

Ale nie będziesz wiedział, który plik się zmienił ...

W tym celu możesz spróbować czegoś takiego

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

Ta strategia jest bardzo przydatna, gdy dwa katalogi, które mają być porównywane, nie znajdują się na tym samym komputerze i musisz upewnić się, że pliki są równe w obu katalogach.

Innym dobrym sposobem na wykonanie zadania jest użycie diffpolecenia Gita (może powodować problemy, gdy pliki mają różne uprawnienia - wtedy każdy plik jest wyświetlany na wyjściu):

git diff --no-index dir1/ dir2/
Adail Junior
źródło
1
Nie działa to bez dodatkowego kroku sortowania, ponieważ kolejność, w jakiej findpliki będą wyświetlane na liście, będzie się zasadniczo różnić między dwoma katalogami.
Faheem Mitha
1
Do sortowania plików można użyć metody opisanej w askubuntu.com/a/662383/15729 .
Faheem Mitha
1
Otrzymuję błąd `` znajdź: md5sum: Nie ma takiego pliku ani katalogu
Houman
1
@Houman Nie wiem, jakiego Linux Distro używasz, ale być może musisz zainstalować pakiet, który zapewni de md5sum. W Fedorze 26 możesz go zainstalować za pomocą: #dnf install coreutils
Adail Junior
Użyj md5 () zamiast
BOJ
81

Możesz użyć diffpolecenia tak samo, jak w przypadku plików:

diff <directory1> <directory2>

Jeśli chcesz zobaczyć także podfoldery i pliki, możesz użyć -ropcji:

diff -r <directory1> <directory2>
Alex R.
źródło
2
Nie wiedziałem, że diffdziała również dla katalogów (potwierdził to man diff), ale to nie rekurencyjnie sprawdza zmiany w podkatalogach wewnątrz podkatalogów.
jobin
1
@Jobin To dziwne ... Dla mnie to działa.
Alex R.
1
Mam coś takiego: a/b/c/d/a, x/b/c/d/b. Zobacz, co diff a xci daje.
jobin
2
Musisz użyć -ropcji. To ( diff -r a x) daje mi:Only in a/b/c/d: a. only in x/b/c/d: b.
Alexa R.
3
diff pokaż mi różnicę w plikach INTO, ale nie jeśli katalog zawiera plik, którego drugi nie zawiera !!! Nie potrzebuję znać różnic w pliku, ale także, jeśli plik istnieje w katalogu, a nie w drugim
AndreaNobili
24

Poprzez nie używasz bash, można to zrobić przy użyciu diff z --briefi --recursive:

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

man diffZawiera obie opcje:

-q, --brief
zgłoś tylko wtedy, gdy pliki się różnią

-r, --recursive
rekurencyjnie porównaj wszystkie znalezione podkatalogi

Braiam
źródło
13

Oto alternatywa, aby porównać tylko nazwy plików, a nie ich zawartość:

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

Jest to prosty sposób na wyświetlenie listy brakujących plików, ale oczywiście nie wykryje plików o tej samej nazwie, ale o innej zawartości!

(Osobiście używam własnego diffdirsskryptu, ale jest to część większej biblioteki ).

joeytwiddle
źródło
3
Lepiej użyj zastępowania procesów, a nie plików tymczasowych ...
mniip
3
Zauważ, że nie obsługuje to nazw plików z pewnymi znakami specjalnymi, w takim przypadku możesz użyć ograniczników zer, których AFAIK diffnie obsługuje obecnie. Ale jest to, commco obsługuje go od czasu git.savannah.gnu.org/cgit/coreutils.git/commit/…, więc jeśli dojdzie do coreutils w pobliżu, możesz to zrobić comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(którego wyniki możesz być zmuszone do dalszej konwersji w formacie musisz użyć --output-delimiterparametru i dodatkowych narzędzi).
phk
7

Być może jedną z opcji jest uruchomienie rsync dwa razy:

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

W poprzednim wierszu otrzymasz pliki, które znajdują się w katalogu 1 i są inne (lub ich brakuje) w katalogu 2.

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

To samo dotyczy 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

Możesz usunąć -nopcję przejścia zmian. To jest kopiowanie listy plików do drugiego folderu.

Jeśli to zrobisz, być może dobrym rozwiązaniem jest użycie -u, aby uniknąć zastąpienia nowszych plików.

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

Jednowarstwowy:

rsync -rtvcsOu -n --progress /dir1/ /dir2/ && rsync -rtvcsOu -n --progress /dir2/ /dir1/
Ferroao
źródło
3

Jeśli chcesz, aby każdy plik był rozwijany i zwijany, możesz przesłać dane wyjściowe diff -rdo Vima.

Najpierw dajmy Vimowi zasadę składania:

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

Teraz tylko:

diff -r dir1 dir2 | vim -

Możesz uderzać zoi zcotwierać i zamykać fałdy. Aby wyjść z Vima, naciśnij:q<Enter>

joeytwiddle
źródło
3

Dość łatwe zadanie do wykonania w pythonie:

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

Zastąp rzeczywiste wartości dla DIR1i DIR2.

Oto przykładowy przebieg:

$ 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

Dla czytelności, oto rzeczywisty skrypt zamiast jednowierszowego:

#!/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")
Sergiy Kolodyazhnyy
źródło
2
Zauważ, że os.listdirnie daje żadnej określonej kolejności. Tak więc listy mogą mieć te same rzeczy w innej kolejności i porównanie się nie powiedzie.
muru
1
@muru dobry punkt, uwzględnię sortowanie do tego
Sergiy Kolodyazhnyy
3

Zainspirowany odpowiedzią Sergiy napisałem własny skrypt Pythona, aby porównać dwa katalogi.

W przeciwieństwie do wielu innych rozwiązań nie porównuje zawartości plików. Nie wchodzi też do podkatalogów, których brakuje w jednym z katalogów. Wynik jest więc dość zwięzły, a skrypt działa szybko z dużymi katalogami.

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

Jeśli zapiszesz go w pliku o nazwie compare_dirs.py, możesz go uruchomić za pomocą Python3.x:

python3 compare_dirs.py dir1 dir2

Przykładowe dane wyjściowe:

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 Jeśli chcesz porównać rozmiary plików i skróty plików pod kątem potencjalnych zmian, opublikowałem zaktualizowany skrypt tutaj: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779

Andriy Makukha
źródło
1
Dzięki, dodałem opcjonalne wyrażenie regularne trzeciego parametru, aby pominąć / zignorować gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684, aby zrobić dokładnie to, czego potrzebowałem:cmpdirs dir1 dir2 '/\.git/'
Mike
0

Dodam do tej listy alternatywę dla NodeJ, którą napisałem jakiś czas temu.

reż-porównaj

npm install dir-compare -g
dircompare dir1 dir2
gliviu
źródło
0

Chciałbym zaproponować świetne narzędzie, które właśnie odkryłem: MELD .

Działa poprawnie i wszystko, co możesz zrobić za pomocą polecenia diffw systemie Linux, można tam replikować za pomocą ładnego interfejsu graficznego! Cieszyć się

Leos313
źródło