Порівнюючи зміст двох каталогів


92

У мене є два каталоги, які повинні містити однакові файли і мати однакову структуру каталогів.

Я думаю, що чогось не вистачає в одному з цих каталогів.

Використовуючи bash shell, чи є спосіб порівняти мої каталоги та побачити, чи в одному з них відсутні файли, які є в інших?


1
Який вихід bash --version?
приєднатися

Відповіді:


63

Хороший спосіб зробити це порівняння - використовувати findз md5sum, потім a diff.

Приклад

Використовуйте пошук, щоб перерахувати всі файли в каталозі, а потім обчислити хеш md5 для кожного файлу та передати його сортування за назвою файлу у файл:

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

Зробіть ту ж процедуру в іншому каталозі:

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

Потім порівняйте результат два файли з diff:

diff -u dir1.txt dir2.txt

Або як одна команда, що використовує підстановку процесу:

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

Якщо ви хочете бачити лише зміни:

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

Команда cut виводить тільки хеш (перше поле), яке порівнюється по diff. В іншому випадку diff буде друкувати кожен рядок, оскільки шляхи до каталогу відрізняються, навіть якщо хеш однаковий.

Але ви не знатимете, який файл змінився ...

Для цього можна спробувати щось подібне

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

Ця стратегія дуже корисна, коли два каталоги, які потрібно порівнювати, не знаходяться в одній машині, і вам потрібно переконатися, що файли в обох каталогах рівні.

Ще один хороший спосіб виконати роботу - це використання diffкоманди Git (може спричинити проблеми, коли файли мають різні дозволи -> кожен файл перерахований у висновку тоді):

git diff --no-index dir1/ dir2/

1
Це не працює без додаткового кроку сортування, оскільки порядок, у якому findбудуть перераховані файли, буде різнитися між двома каталогами.
Faheem Mitha

1
Для сортування файлів можна використовувати метод, описаний у askubuntu.com/a/662383/15729 .
Faheem Mitha

1
Я отримую помилку `` find: md5sum: Немає такого файлу чи каталогу
Houman

1
@Houman Я не знаю, яким Linux Distro ви користуєтесь, але, можливо, вам потрібно встановити пакет, який надасть de md5sum. У Fedora 26 ви можете встановити його за допомогою: #dnf встановити coreutils
Adail Junior

Використовуйте замість md5 ()
бой

81

Ви можете використовувати diffкоманду так само, як і для файлів:

diff <directory1> <directory2>

Якщо ви також хочете бачити підпапки та -файли, ви можете скористатися -rопцією:

diff -r <directory1> <directory2>

2
Не знав також, що diffпрацює і для каталогів (man diff підтвердив це), але це не рекурсивно перевіряє зміни в підкаталогах всередині підкаталогів.
jobin

1
@Jobin Це дивно ... Для мене це працює.
Алекс Р.

1
У мене є щось подібне: a/b/c/d/a, x/b/c/d/b. Подивіться, що diff a xдає вам.
Jobin

2
Ви повинні використовувати -rопцію. Це ( diff -r a x) дає мені:Only in a/b/c/d: a. only in x/b/c/d: b.
Алекс Р.

3
diff, покажіть мені різницю файлів INTO, але ні, якщо каталог містить файл, який не містить іншого !!! Мені не потрібно знати відмінності у файлі, але також, якщо файл існує в каталозі, а не в іншому
AndreaNobili

24

Через те, що ви не використовуєте bash, ви можете це зробити, використовуючи diff --briefі --recursive:

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

man diffВключає в себе обидва варіанти:

-q, --brief
звітувати лише тоді, коли файли відрізняються

-r, --recursive
рекурсивно порівнюйте знайдені підкаталоги


13

Ось альтернатива, щоб порівняти лише імена файлів, а не їх вміст:

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

Це простий спосіб переліку відсутніх файлів, але, звичайно, він не виявить файли з тим самим іменем, але різним вмістом!

(Особисто я використовую власний diffdirsсценарій, але це частина більшої бібліотеки .)


3
Ви б краще скористатися процедурою заміни, а не тимчасовими файлами ...
mniip

3
Зауважте, що це не підтримує імена файлів з певними спеціальними символами, у цьому випадку ви можете використовувати розмежувачі нулів, які AFAIK diffне підтримує на сьогодні. Але є те, commщо підтримує його з git.savannah.gnu.org/cgit/coreutils.git/commit/…, тож як тільки справа доходить до ядер, які знаходяться поблизу вас, ви можете це зробити comm -z <(cd folder1 && find -print0 | sort) <(cd folder2 && find -print0 | sort -z)(чий вихід, можливо, доведеться додатково конвертувати у формат вам потрібно скористатися --output-delimiterпараметром та додатковими інструментами).
phk

7

Можливо, одним із варіантів є запустити rsync два рази:

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

З попереднього рядка ви отримаєте файли, які знаходяться у dir1 та різні (або відсутні) в dir2.

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

Те ж саме для 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

Ви можете видалити -nпараметр зазнати змін. Це копіювання списку файлів у другу папку.

Якщо ви це зробите, можливо, хорошим варіантом є використання -u, щоб уникнути перезапису нових файлів.

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

Одноколісний:

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

3

Якщо ви хочете зробити кожен файл розширюваним і збірним, ви можете передавати вихід diff -rу Vim.

Спочатку давайте наведемо Vim правило складання:

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

Тепер просто:

diff -r dir1 dir2 | vim -

Можна вдарити zoі zcвідкрити та закрити складки. Щоб вийти з Vim, натисніть:q<Enter>


3

Досить легке завдання в пітоні:

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

Замініть фактичні значення для DIR1та DIR2.

Ось приклад запуску:

$ 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

Для зручності для читання ось фактичний сценарій замість однолінійного:

#!/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
Зауважте, що os.listdirконкретний наказ не дає. Тож у списках можуть бути однакові речі в іншому порядку, і порівняння не вдасться.
муру

1
@muru good point, я включаю сортування до цього
Сергій Колодяжний

3

Натхненний відповіддю Сергія, я написав власний сценарій Python для порівняння двох каталогів.

На відміну від багатьох інших рішень, він не порівнює вміст файлів. Крім того, він не входить до підкаталогів, які відсутні в одному з каталогів. Тож вихід досить стислий, а сценарій працює швидко з великими каталогами.

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

Якщо ви збережете його у файлі з назвою compare_dirs.py, можете запустити його за допомогою Python3.x:

python3 compare_dirs.py dir1 dir2

Вибірка зразка:

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 Якщо вам потрібно порівняти розміри файлів та хеші файлів для можливих змін, я опублікував оновлений скрипт тут: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779


1
Дякую, я додав необов'язковий третій парам- парафікс, щоб пропустити / проігнорувати gist.github.com/mscalora/e86e2bbfd3c24a7c1784f3d692b1c684, щоб зробити саме те, що мені потрібно:cmpdirs dir1 dir2 '/\.git/'
Майк


0

Я хотів би запропонувати чудовий інструмент, який я щойно відкрив: MELD .

Вона працює належним чином, і все, що ви можете зробити з командою diffна базі системи Linux, може бути тиражировано приємним графічним інтерфейсом! Насолоджуйтесь

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.