Сравнение содержимого двух каталогов

У меня есть два каталога, которые должны содержать одни и те же файлы и иметь одинаковую структуру каталогов.

Я думаю, что в одном из этих каталогов чего-то не хватает.

Используя оболочку bash, есть ли способ сравнить мои каталоги и посмотреть, отсутствуют ли в одном из них файлы, которые присутствуют в другом?

Вы можете использовать diff команда так же, как вы бы использовали ее для файлов:

diff <directory1> <directory2>

Если вы также хотите просмотреть вложенные папки и -files, вы можете использовать -r вариант:

diff -r <directory1> <directory2>

Хороший способ сделать это сравнение - использовать find с md5sum, а затем 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/ .*\// /')

Эта стратегия очень полезна, когда два сравниваемых каталога находятся не на одном компьютере, и вам нужно убедиться, что файлы одинаковы в обоих каталогах.

Еще один хороший способ выполнить эту работу - использовать Git's diff> команда (может вызвать проблемы, когда файлы имеют разные разрешения - тогда каждый файл будет указан в выходных данных):

git diff --no-index dir1/ dir2/

Поскольку вы не используете bash, вы можете сделать это с помощью diff с --brief и --recursive:

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

То man diff включает в себя оба варианта:

-q, --brief
сообщать только в том случае, если файлы отличаются

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

Возможно, один из вариантов - запустить rsync два раза:

rsync -rtOvcs --progress -n /dir1/ /dir2/

С помощью предыдущей строки вы получите файлы, которые находятся в dir1 и отличаются (или отсутствуют) в dir2.

rsync -rtOvcs --progress -n /dir2/ /dir1/

То же самое для dir2

#from the rsync --help :-n, --dry-run               perform a trial run with no changes made-r, --recursive             recurse into directories-t, --times                 preserve modification times-O, --omit-dir-times        omit directories from --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

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

В случае, если вы это сделаете, возможно, хорошим вариантом будет использовать -u, чтобы избежать перезаписи новых файлов.

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

Однострочный:

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

Вот альтернатива, чтобы сравнить только имена файлов, а не их содержимое:

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

Это простой способ перечислить отсутствующие файлы, но, конечно, это не будет обнаруживать файлы с одинаковым именем, но разным содержимым!

(Лично я использую свой собственный diffdirs сценарий, но это часть большая библиотека.)

Я хотел бы предложить отличный инструмент, который я только что обнаружил: СЛИЯНИЕ.

Он работает правильно, и все, что вы можете сделать с помощью команды diff в системе на базе Linux, может быть там воспроизведен с приятным графическим интерфейсом!

Например, в сравнение каталогов это просто:

directories comparison

а также в сравнение файлов делается проще:

files comparison

Существует хорошая интеграция с некоторой контрольной версией (например, Git) и может использоваться как инструмент слияния. Смотрите полную документацию на его веб-сайте.

Вдохновленный ответом Сергея, я написал свой собственный скрипт на Python для сравнения двух каталогов.

В отличие от многих других решений, он не сравнивает содержимое файлов. Кроме того, он не входит в подкаталоги, которые отсутствуют в одном из каталогов. Таким образом, вывод довольно лаконичен, и скрипт быстро работает с большими каталогами.

#!/usr/bin/env python3import os, sysdef 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 removedDIR  new/out/flavor-maxim2 addedDIR  old/target/vendor/flavor-domino removedDIR  new/target/vendor/flavor-maxim2 addedFILE old/tmp/.kconfig-flavor_domino removedFILE new/tmp/.kconfig-flavor_maxim2 addedDIR  new/tools/tools/LiveSuit_For_Linux64 added

P.S. Если вам нужно сравнить размеры файлов и хэши файлов на предмет возможных изменений, я опубликовал обновленный скрипт здесь: https://gist.github.com/amakukha/f489cbde2afd32817f8e866cf4abe779

Довольно простая задача для достижения в python:

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/ DesktopSAME$ 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 pythonimport os, sysd1 = os.listdir(sys.argv[1])d2 = os.listdir(sys.argv[2])d1.sort()d2.sort()if d1 == d2:    print("SAME")else:    print("DIFF")

Если вы хотите сделать каждый файл расширяемым и сворачиваемым, вы можете передать выходные данные diff -r в Vim.

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

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

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

diff -r dir1 dir2 | vim - -R

Вы можете ударить zo и zc чтобы открывать и закрывать складки. Чтобы выйти из Vim, нажмите :q<Enter>

То -R является необязательным, но я нахожу его полезным наряду - потому что это останавливает Vim от прослушивания вас, чтобы сохранить буфер, когда вы выходите.

Хороший ответ Adail Junior может привести к проблемам со временем выполнения, если у вас есть сотни тысяч файлов! Итак, вот еще один способ сделать это.Допустим, вы хотите сравнить все имена файлов папки A со всеми именами файлов папки B.Шаг 1, вставьте компакт-диск в папку A и выполните:

find . | sort -k 2 > listA.txt

Шаг 2, вставьте компакт-диск в папку B и выполните:

find . | sort -k 2 > listB.txt

Шаг 3, возьмите разницу в listA.txt и listB.txt

Я попробовал это в папках, содержащих полмиллиона текстовых файлов, и менее чем за 30 секунд у меня на экране появился diff, в то время как вычисление сумм md5, а затем передача и добавление могут занять очень много времени. Обратите также внимание, что в исходном вопросе предлагается сравнить имена файлов (а не их содержимое!) и проверить, отсутствуют ли файлы между сравниваемыми папками! Спасибо

Каков результат bash --version?

Аналогично, но более конкретно: linux - Find the files existing in one directory but not in the other - Stack Overflow