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

93

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

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

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

AndreaNobili
источник
1
Какой выход bash --version?
Jobin
1
Аналогично, но более конкретно: stackoverflow.com/questions/16787916/…
Чиро Сантилли 新疆 15 中心 法轮功 六四 事件

Ответы:

64

Хороший способ сделать это сравнение - использовать findс md5sum, а затем diff.

пример

Используйте find, чтобы вывести список всех файлов в каталоге, затем вычислить хэш 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/
Adail Junior
источник
1
Это не работает без дополнительного шага сортировки, потому что порядок, в котором findбудут перечислены файлы, будет в целом отличаться между двумя каталогами.
Фахим Митха
1
Можно использовать метод, описанный в askubuntu.com/a/662383/15729 для сортировки файлов.
Фахим Митха
1
Я получаю ошибку `` find: md5sum: Нет такого файла или каталога
Houman
1
@Houman Я не знаю, какой дистрибутив Linux вы используете, но, возможно, вам нужно установить пакет, который предоставит de md5sum. В Fedora 26 вы можете установить его с помощью: #dnf install coreutils
Adail Junior
Вместо этого используйте md5 ()
Boj
81

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

diff <directory1> <directory2>

Если вы хотите увидеть вложенные папки и -files, вы можете использовать -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
25

Через вы не используете Баш, вы можете сделать это с помощью диф с --briefи --recursive:

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

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

-q, --brief
Отчет только тогда , когда файлы отличаются

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

Braiam
источник
13

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

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

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

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

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

Может быть, один из вариантов - запустить 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/
Ferroao
источник
3

Если вы хотите сделать каждый файл расширяемым и сворачиваемым, вы можете направить вывод diff -rв 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>

joeytwiddle
источник
3

Довольно простая задача для достижения в 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/ 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
@ Муру, хороший момент, я включу сортировку к этому
Сергей Колодяжный
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

Я добавлю в этот список альтернативу NodeJ, которую я написал некоторое время назад.

реж-сравнить

npm install dir-compare -g
dircompare dir1 dir2
gliviu
источник
0

Я хотел бы предложить отличный инструмент, который я только что открыл: MELD .

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

Leos313
источник