Как получить MD5-сумму содержимого каталога в виде одной суммы?

Как получить MD5-сумму содержимого каталога в виде одной суммы?

Программа md5sum не предоставляет контрольные суммы для каталогов. Я хочу получить одну контрольную сумму MD5 для всего содержимого каталога, включая файлы в подкаталогах. То есть одну объединенную контрольную сумму, составленную из всех файлов. Есть ли способ сделать это?

решение1

Правильный путь зависит от того, почему именно вы спрашиваете:

Вариант 1: Сравнение только данных

Если вам нужен просто хэш содержимого файла дерева, то это подойдет:

$ find -s somedir -type f -exec md5sum {} \; | md5sum

Сначала он суммирует содержимое всех файлов по отдельности в предсказуемом порядке, затем передает этот список имен файлов и хэшей MD5 для хеширования, давая единое значение, которое изменяется только при изменении содержимого одного из файлов в дереве.

К сожалению, find -sработает только с BSD find(1), используемым в macOS, FreeBSD, NetBSD и OpenBSD. Чтобы получить что-то сопоставимое в системе с GNU или SUS find(1), вам нужно что-то немного уродливее:

$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum

Мы имитировали поведение BSD, find -sдобавив вызов sort. -k 2Бит сообщает ему о необходимости пропустить хэш MD5, поэтому он сортирует только имена файлов, которые находятся в поле 2 до конца строки по sortподсчету .

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

В любом случае сортировка необходима для избежания ложных срабатываний: наиболее распространенные файловые системы Unix/Linux не поддерживают списки каталогов в стабильном, предсказуемом порядке. Вы можете не осознавать этого, используя lsи подобные, которые молча сортируют содержимое каталога для вас. Вызов findбез сортировки его вывода каким-либо образом приведет к тому, что порядок строк в выводе будет соответствовать порядку, который возвращает им базовая файловая система, что приведет к тому, что эта команда выдаст измененное значение хеша, если порядок файлов, указанных ей в качестве входных данных, изменится, даже если данные останутся идентичными.

Вы можете спросить, необходим ли бит -k 2в команде GNU sortвыше. Учитывая, что хэш данных файла является адекватным прокси для имени файла, пока содержимое не изменилось, мы не получим ложных срабатываний, если откажемся от этой опции, что позволит нам использовать одну и ту же команду как с GNU, так и с BSD sort. Однако следует понимать, что существует небольшая вероятность (1:2 128 с MD5), что точный порядок имен файлов не будет соответствовать частичному порядку, который -k 2может быть получен без него, если когда-либо возникнет коллизия хэшей. Однако имейте в виду, если такие небольшие шансы несоответствия имеют значение для вашего приложения, весь этот подход, вероятно, для вас не подходит.

Вам может потребоваться изменить md5sumкоманды на md5или какую-либо другую хэш-функцию. Если вы выбрали другую хэш-функцию и вам нужна вторая форма команды для вашей системы, вам может потребоваться sortсоответствующим образом настроить команду. Другая ловушка заключается в том, что некоторые программы суммирования данных вообще не записывают имя файла, ярким примером является старая sumпрограмма Unix.

Этот метод несколько неэффективен, поскольку вызывается md5sumN+1 раз, где N — количество файлов в дереве, но это необходимые затраты, чтобы избежать хеширования метаданных файлов и каталогов.

Вариант 2: Сравнение данныхиМетаданные

Если вам нужно иметь возможность обнаружить эточто-либов дереве изменилось не только содержимое файла, попросите tarупаковать для вас содержимое каталога, а затем отправьте его по адресу md5sum:

$ tar -cf - somedir | md5sum

Поскольку tarтакже видны права доступа к файлам, права собственности и т. д., это также обнаружит изменения этих вещей, а не только изменения содержимого файлов.

Этот метод значительно быстрее, поскольку он делает только один проход по дереву и запускает хеш-программу только один раз.

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

Если вы обнаружите, что получаете ложные срабатывания, я бы рекомендовал выбрать find | cpioопциюответ Жиля.

решение2

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

Данные и метаданные

Создание архива, содержащего файлы, — хорошее начало. Это однозначное представление (очевидно, поскольку вы можете восстановить файлы, извлекая архив). Оно может включать метаданные файлов, такие как даты и владельца. Однако это пока не совсем верно: архив неоднозначен, поскольку его представление зависит от порядка, в котором хранятся файлы, и, если применимо, от сжатия.

Решением является сортировка имен файлов перед их архивацией. Если имена файлов не содержат новых строк, вы можете запустить find | sort, чтобы получить их список и добавить их в архив в этом порядке. Обязательно укажите архиватору не выполнять рекурсию в каталоги. Вот примеры с POSIX pax, GNU tar и cpio:

find | LC_ALL=C sort | pax -w -d | md5sum
find | LC_ALL=C sort | tar -cf - -T - --no-recursion | md5sum
find | LC_ALL=C sort | cpio -o | md5sum

Только имена и содержимое, низкотехнологичный способ

Если вы хотите учитывать только данные файла, а не метаданные, вы можете создать архив, включающий только содержимое файла, но для этого нет стандартных инструментов. Вместо включения содержимого файла вы можете включить хэш файлов. Если имена файлов не содержат новых строк и есть только обычные файлы и каталоги (без символических ссылок или специальных файлов), это довольно просто, но вам нужно позаботиться о нескольких вещах:

{ export LC_ALL=C;
  find -type f -exec wc -c {} \; | sort; echo;
  find -type f -exec md5sum {} + | sort; echo;
  find . -type d | sort; find . -type d | sort | md5sum;
} | md5sum

Мы включаем список каталогов в дополнение к списку контрольных сумм, так как в противном случае пустые каталоги были бы невидимы. Список файлов сортируется (в определенной, воспроизводимой локали — спасибо Peter.O за то, что напомнил мне об этом). разделяет echoдве части (без этого вы могли бы сделать некоторые пустые каталоги, чьи имена выглядели бы как md5sumвывод, который также мог бы сойти за обычные файлы). Мы также включаем список размеров файлов, чтобы избежатьатаки по удлинению.

Кстати, MD5 устарел. Если он доступен, рассмотрите возможность использования SHA-2 или хотя бы SHA-1.

Имена и данные, поддержка переносов строк в именах

Вот вариант кода выше, который использует инструменты GNU для разделения имен файлов нулевыми байтами. Это позволяет именам файлов содержать новые строки. Утилиты GNU digest заключают специальные символы в кавычки в своих выходных данных, поэтому не будет неоднозначных новых строк.

{ export LC_ALL=C;
  du -0ab | sort -z; # file lengths, including directories (with length 0)
  echo | tr '\n' '\000'; # separator
  find -type f -exec sha256sum {} + | sort -z; # file hashes
  echo | tr '\n' '\000'; # separator
  echo "End of hashed data."; # End of input marker
} | sha256sum

Более надежный подход

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

#! /usr/bin/env python
import hashlib, hmac, os, stat, sys
## Return the hash of the contents of the specified file, as a hex string
def file_hash(name):
    f = open(name)
    h = hashlib.sha256()
    while True:
        buf = f.read(16384)
        if len(buf) == 0: break
        h.update(buf)
    f.close()
    return h.hexdigest()
## Traverse the specified path and update the hash with a description of its
## name and contents
def traverse(h, path):
    rs = os.lstat(path)
    quoted_name = repr(path)
    if stat.S_ISDIR(rs.st_mode):
        h.update('dir ' + quoted_name + '\n')
        for entry in sorted(os.listdir(path)):
            traverse(h, os.path.join(path, entry))
    elif stat.S_ISREG(rs.st_mode):
        h.update('reg ' + quoted_name + ' ')
        h.update(str(rs.st_size) + ' ')
        h.update(file_hash(path) + '\n')
    else: pass # silently symlinks and other special files
h = hashlib.sha256()
for root in sys.argv[1:]: traverse(h, root)
h.update('end\n')
print h.hexdigest()

решение3

Если ваша цель — просто найти различия между двумя каталогами, рассмотрите возможность использования diff.

Попробуй это:

diff -qr dir1 dir2

решение4

Использоватьchecksumdir:

$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad

БыстрееиПолегчечем другие решения bash.

Связанный контент