Linux: вычислить один хэш для данной папки и содержимого?


98

Конечно, должен быть способ сделать это легко!

Я пробовал приложения командной строки Linux, такие как sha1sumи, md5sumно они, похоже, могут вычислять только хэши отдельных файлов и выводить список значений хэшей, по одному для каждого файла.

Мне нужно сгенерировать единый хеш для всего содержимого папки (а не только для имен файлов).

Я бы хотел сделать что-то вроде

sha1sum /folder/of/stuff > singlehashvalue

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


1
Под «полным содержимым» вы имеете в виду логические данные всех файлов в каталоге или его данные вместе с метаданными при получении корневого хэша? Поскольку критерии выбора вашего варианта использования довольно широки, в своем ответе я попытался затронуть несколько практических.
six-k

Ответы:


124

Один из возможных способов:

sha1sum путь / к / папке / * | sha1sum

Если есть целое дерево каталогов, вам, вероятно, лучше использовать find и xargs. Одна из возможных команд была бы

найти путь / к / папке -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

И, наконец, если вам также нужно учитывать разрешения и пустые каталоги:

(find path/to/folder -type f -print0  | sort -z | xargs -0 sha1sum;
 find path/to/folder \( -type f -o -type d \) -print0 | sort -z | \
   xargs -0 stat -c '%n %a') \
| sha1sum

Аргументы statзаставят его напечатать имя файла, а затем его восьмеричные разрешения. Эти два поиска будут выполняться один за другим, что приведет к удвоению количества операций ввода-вывода на диске: первое обнаружит все имена файлов и вычислит контрольную сумму содержимого, второе обнаружит все имена файлов и каталогов, имя и режим печати. Список «имен файлов и контрольных сумм», за которым следуют «имена и каталоги с разрешениями», затем будет суммирован для получения меньшей контрольной суммы.


2
и не забудьте установить LC_ALL = POSIX, чтобы различные инструменты создавали вывод, независимый от локали.
Дэвид Шмитт

2
Я нашла кота | sha1sum будет значительно быстрее, чем sha1sum | sha1sum. YMMV, попробуйте каждый из них в своей системе: время найти путь / к / папке -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; время найти путь / к / папке -type f -print0 | sort -z | xargs -0 cat | sha1sum
Бруно Броноски

5
@RichardBronosky - Предположим, у нас есть два файла, A и B. A содержит «foo», а B содержит «bar was here». С помощью вашего метода мы не сможем отделить это от двух файлов C и D, где C содержит «foobar», а D содержит «было здесь». Хешируя каждый файл по отдельности, а затем хешируя все пары «хеш имени файла», мы можем увидеть разницу.
Vatine

2
Чтобы эта работа работала независимо от пути к каталогу (т.е. когда вы хотите сравнить хэши двух разных папок), вам нужно использовать относительный путь и перейти в соответствующий каталог, потому что пути включены в окончательный хэш:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles

3
@robbles Это правильно и почему я не поставил инициалы /на path/to/folderбит.
Vatine

26
  • Используйте средство обнаружения вторжений в файловую систему, например помощник .

  • хэш tar-мяч каталога:

    tar cvf - /path/to/folder | sha1sum

  • Кодируйте что-нибудь самостоятельно, например, vatine's oneliner :

    find /path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum


3
+1 за раствор дегтя. Это самый быстрый способ, но отбросить v. Многословие только замедлит его.
Бруно Броноски

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

5
Хеш git не подходит для этой цели, поскольку содержимое файла является только частью его ввода. Даже для начальной фиксации ветки на хэш влияет сообщение фиксации, а также метаданные фиксации, например время фиксации. Если вы фиксируете одну и ту же структуру каталогов несколько раз, вы будете каждый раз получать разный хэш, поэтому полученный хеш не подходит для определения того, являются ли два каталога точными копиями друг друга, просто отправив хеш.
Золтан

1
@Zoltan, хеш git вполне подойдет, если вы используете хеш дерева, а не хеш фиксации.
hobbs

1
@hobbs Первоначально в ответе было указано «хеш фиксации», что, конечно, не подходит для этой цели. Хеш дерева кажется гораздо лучшим кандидатом, но все же могут быть скрытые ловушки. Мне приходит в голову, что установка бита исполняемого файла для некоторых файлов изменяет хеш дерева. Чтобы git config --local core.fileMode falseэтого избежать, вы должны решить проблему, прежде чем совершать какие-либо действия . Не знаю, есть ли еще подобные предостережения.
Золтан

14

Ты можешь сделать tar -c /path/to/folder | sha1sum


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

2
обоснованные опасения slowdog в несмотря на это , если вы заботитесь о содержимом файлов, разрешений и т.д. , но не изменение времени, вы можете добавить --mtimeопцию следующим образом: tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
Binary Phile

@ S.Lott, если размер каталога большой, я имею в виду, что если размер каталога такой большой, его архивирование и получение md5 займет больше времени
Касун Сиямбалапития

13

Если вы просто хотите проверить, не изменилось ли что-то в папке, я бы порекомендовал это:

ls -alR --full-time /folder/of/stuff | sha1sum

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

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


1
Я не уверен, почему у этого нет большего количества голосов, учитывая простоту решения. Может ли кто-нибудь объяснить, почему это не сработает?
Dave C

1
Я полагаю, это не идеально, поскольку сгенерированный хеш будет основан на владельце файла, настройке формата даты и т. Д.
Ryota

1
Команду ls можно настроить для вывода того, что вы хотите. Вы можете заменить -l на -gG, чтобы опустить группу и владельца. И вы можете изменить формат даты с помощью опции --time-style. В основном просмотрите страницу руководства ls и посмотрите, что вам подходит.
Shumoapp

@DaveC Потому что это бесполезно. Если вы хотите сравнить имена файлов, просто сравните их напрямую. Они не такие уж большие.
Navin

7
@Navin Из вопроса неясно, нужно ли хешировать содержимое файла или обнаруживать изменение в дереве. У каждого случая есть свои применения. Например, хранение 45 КБ имен файлов в дереве ядра менее практично, чем один хэш. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum отлично работает для меня
yashma

5

Надежный и чистый подход

  • Перво-наперво, не перегружайте доступную память ! Хешируйте файл по частям, а не загружайте файл целиком.
  • Различные подходы для разных нужд / целей (все из перечисленных ниже или выберите то, что когда-либо применимо):
    • Хешировать только имя записи всех записей в дереве каталогов
    • Хешируйте содержимое файла всех записей (оставив мета, например, номер inode, ctime, atime, mtime, размер и т. Д., Вы поняли)
    • Для символической ссылки ее содержанием является имя референта. Хешируйте его или выберите пропустить
    • Следовать или не следовать (разрешенное имя) символической ссылке при хешировании содержимого записи
    • Если это каталог, его содержимое - это просто записи каталога. При рекурсивном обходе они в конечном итоге будут хешированы, но следует ли хешировать имена записей каталога этого уровня, чтобы пометить этот каталог? Полезно в случаях использования, когда хеш требуется для быстрого определения изменения без необходимости глубокого обхода для хеширования содержимого. Примером может быть изменение имени файла, но остальное содержимое остается прежним, и все они являются довольно большими файлами.
    • Хорошо обрабатывайте большие файлы (опять же, обратите внимание на оперативную память)
    • Обработка очень глубоких деревьев каталогов (обратите внимание на дескрипторы открытых файлов)
    • Обработка нестандартных имен файлов
    • Как продолжить работу с файлами, которые являются сокетами, каналами / FIFO, блочными устройствами, символьными устройствами? Их тоже нужно хешировать?
    • Не обновляйте время доступа к любой записи во время обхода, потому что это будет побочным эффектом и контрпродуктивным (интуитивно понятным?) Для определенных случаев использования.

Это то, что у меня в голове, любой, кто потратил некоторое время на работу над этим, практически поймал бы другие ловушки и угловые случаи.

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

Пример использования и вывода dtreetrawl.

Usage:
  dtreetrawl [OPTION...] "/trawl/me" [path2,...]

Help Options:
  -h, --help                Show help options

Application Options:
  -t, --terse               Produce a terse output; parsable.
  -j, --json                Output as JSON
  -d, --delim=:             Character or string delimiter/separator for terse output(default ':')
  -l, --max-level=N         Do not traverse tree beyond N level(s)
  --hash                    Enable hashing(default is MD5).
  -c, --checksum=md5        Valid hashing algorithms: md5, sha1, sha256, sha512.
  -R, --only-root-hash      Output only the root hash. Blank line if --hash is not set
  -N, --no-name-hash        Exclude path name while calculating the root checksum
  -F, --no-content-hash     Do not hash the contents of the file
  -s, --hash-symlink        Include symbolic links' referent name while calculating the root checksum
  -e, --hash-dirent         Include hash of directory entries while calculating root checksum

Фрагмент удобного для человека вывода:

...
... //clipped
...
/home/lab/linux-4.14-rc8/CREDITS
        Base name                    : CREDITS
        Level                        : 1
        Type                         : regular file
        Referent name                :
        File size                    : 98443 bytes
        I-node number                : 290850
        No. directory entries        : 0
        Permission (octal)           : 0644
        Link count                   : 1
        Ownership                    : UID=0, GID=0
        Preferred I/O block size     : 4096 bytes
        Blocks allocated             : 200
        Last status change           : Tue, 21 Nov 17 21:28:18 +0530
        Last file access             : Thu, 28 Dec 17 00:53:27 +0530
        Last file modification       : Tue, 21 Nov 17 21:28:18 +0530
        Hash                         : 9f0312d130016d103aa5fc9d16a2437e

Stats for /home/lab/linux-4.14-rc8:
        Elapsed time     : 1.305767 s
        Start time       : Sun, 07 Jan 18 03:42:39 +0530
        Root hash        : 434e93111ad6f9335bb4954bc8f4eca4
        Hash type        : md5
        Depth            : 8
        Total,
                size           : 66850916 bytes
                entries        : 12484
                directories    : 763
                regular files  : 11715
                symlinks       : 6
                block devices  : 0
                char devices   : 0
                sockets        : 0
                FIFOs/pipes    : 0

1
Не могли бы вы привести краткий пример получения надежного и чистого sha256 папки, возможно, для папки Windows с тремя подкаталогами и несколькими файлами в каждом?
Ферит

3

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

cat $FILES | md5sum

При вычислении хэша убедитесь, что файлы расположены в том же порядке:

cat $(echo $FILES | sort) | md5sum

Но у вас не может быть каталогов в вашем списке файлов.


2
Перемещение конца одного файла в начало следующего за ним файла по алфавиту не повлияет на хэш, но должно. В хэш необходимо включить разделитель или длину файла.
Джейсон Стэнгроум,

3

Еще один инструмент для этого:

http://md5deep.sourceforge.net/

Как это звучит: как md5sum, но также рекурсивно, плюс другие функции.


1
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если ссылка на страницу изменится.
Mamoun Benghezal

3

Если это репозиторий git, и вы хотите игнорировать любые файлы в нем .gitignore, вы можете использовать это:

git ls-files <your_directory> | xargs sha256sum | cut -d" " -f1 | sha256sum | cut -d" " -f1

У меня это хорошо работает.


Большое спасибо! :)
visortelle

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

2

Для этого есть скрипт на Python:

http://code.activestate.com/recipes/576973-getting-the-sha-1-or-md5-hash-of-a-directory/

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


2

Мне пришлось проверять весь каталог на предмет изменений в файлах.

Но без учета временных меток и прав собственности на каталоги.

Задача - получить одинаковую везде сумму, если файлы идентичны.

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

md5sum * | md5sum | cut -d' ' -f1

Он генерирует список хэшей по файлам, а затем объединяет эти хэши в один.

Это намного быстрее, чем метод tar.

Для большей конфиденциальности в наших хэшах мы можем использовать sha512sum по тому же рецепту.

sha512sum * | sha512sum | cut -d' ' -f1

Хеши также идентичны везде, где используется sha512sum, но нет известного способа отменить его.


Это кажется намного проще, чем принятый ответ для хеширования каталога. Я не считал принятый ответ надежным. Одна проблема ... есть ли шанс, что хэши могут появиться в другом порядке? sha256sum /tmp/thd-agent/* | sortэто то, что я пытаюсь сделать для надежного упорядочивания, а затем просто хэширую это.
thinktt

Привет, похоже, хеши по умолчанию идут в алфавитном порядке. Что вы подразумеваете под надежным заказом? Вы должны все это организовать сами. Например, используя ассоциативные массивы, запись + хеш. Затем вы сортируете этот массив по записям, это дает список вычисленных хэшей в порядке сортировки. Я считаю, что вы можете использовать объект json в противном случае и напрямую хешировать весь объект.
NVRM

Если я понимаю, что вы говорите, он хеширует файлы в алфавитном порядке. Это кажется правильным. Что-то в принятом ответе выше иногда отдавало мне прерывистые разные приказы, поэтому я просто пытаюсь убедиться, что это больше не повторится. Я собираюсь поставить сортировку в конце. Кажется, работает. Единственная проблема с этим методом и принятым ответом, который я вижу, - это не вложенные папки. В моем случае у меня нет папок, так что это отлично работает.
thinktt

о чем ls -r | sha256sum?
NVRM

@NVRM попробовал это, и он просто проверил изменения имени файла, а не его содержимое
Gi0rgi0s,

1

Попробуйте сделать это в два этапа:

  1. создать файл с хешами для всех файлов в папке
  2. хэшировать этот файл

Вот так:

# for FILE in `find /folder/of/stuff -type f | sort`; do sha1sum $FILE >> hashes; done
# sha1sum hashes

Или сделать все сразу:

# cat `find /folder/of/stuff -type f | sort` | sha1sum

for F in 'find ...' ...не работает, когда в именах есть пробелы (что в наши дни всегда так).
mivk

1

Я бы перенаправил результаты для отдельных файлов sort(чтобы предотвратить простое переупорядочение файлов для изменения хэша) в md5sumили sha1sum, в зависимости от того, что вы выберете.


1

Для этого я написал сценарий Groovy:

import java.security.MessageDigest

public static String generateDigest(File file, String digest, int paddedLength){
    MessageDigest md = MessageDigest.getInstance(digest)
    md.reset()
    def files = []
    def directories = []

    if(file.isDirectory()){
        file.eachFileRecurse(){sf ->
            if(sf.isFile()){
                files.add(sf)
            }
            else{
                directories.add(file.toURI().relativize(sf.toURI()).toString())
            }
        }
    }
    else if(file.isFile()){
        files.add(file)
    }

    files.sort({a, b -> return a.getAbsolutePath() <=> b.getAbsolutePath()})
    directories.sort()

    files.each(){f ->
        println file.toURI().relativize(f.toURI()).toString()
        f.withInputStream(){is ->
            byte[] buffer = new byte[8192]
            int read = 0
            while((read = is.read(buffer)) > 0){
                md.update(buffer, 0, read)
            }
        }
    }

    directories.each(){d ->
        println d
        md.update(d.getBytes())
    }

    byte[] digestBytes = md.digest()
    BigInteger bigInt = new BigInteger(1, digestBytes)
    return bigInt.toString(16).padLeft(paddedLength, '0')
}

println "\n${generateDigest(new File(args[0]), 'SHA-256', 64)}"

Вы можете настроить использование, чтобы избежать печати каждого файла, изменить дайджест сообщения, удалить хеширование каталога и т. Д. Я проверил его на тестовых данных NIST, и он работает, как ожидалось. http://www.nsrl.nist.gov/testdata/

gary-macbook:Scripts garypaduana$ groovy dirHash.groovy /Users/garypaduana/.config
.DS_Store
configstore/bower-github.yml
configstore/insight-bower.json
configstore/update-notifier-bower.json
filezilla/filezilla.xml
filezilla/layout.xml
filezilla/lockfile
filezilla/queue.sqlite3
filezilla/recentservers.xml
filezilla/sitemanager.xml
gtk-2.0/gtkfilechooser.ini
a/
configstore/
filezilla/
gtk-2.0/
lftp/
menus/
menus/applications-merged/

79de5e583734ca40ff651a3d9a54d106b52e94f1f8c2cd7133ca3bbddc0c6758

0

Вы можете sha1sumсгенерировать список хеш-значений, а затем sha1sumэтот список снова, это зависит от того, что именно вы хотите выполнить.


0

Вот простой и короткий вариант в Python 3, который отлично работает для файлов небольшого размера (например, исходное дерево или что-то в этом роде, где каждый файл индивидуально может легко поместиться в ОЗУ), игнорируя пустые каталоги, на основе идей из других решений:

import os, hashlib

def hash_for_directory(path, hashfunc=hashlib.sha1):                                                                                            
    filenames = sorted(os.path.join(dp, fn) for dp, _, fns in os.walk(path) for fn in fns)         
    index = '\n'.join('{}={}'.format(os.path.relpath(fn, path), hashfunc(open(fn, 'rb').read()).hexdigest()) for fn in filenames)               
    return hashfunc(index.encode('utf-8')).hexdigest()                          

Это работает так:

  1. Рекурсивно найти все файлы в каталоге и отсортировать их по имени
  2. Вычислить хэш (по умолчанию: SHA-1) каждого файла (считывает весь файл в память)
  3. Сделайте текстовый индекс со строками «filename = hash»
  4. Закодируйте этот индекс обратно в байтовую строку UTF-8 и хэш, который

Вы можете передать другую хеш-функцию в качестве второго параметра, если SHA-1 вам не подходит.


0

Пока что самый быстрый способ сделать это - использовать tar. А с помощью нескольких дополнительных параметров мы также можем избавиться от разницы, вызванной метаданными.

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

tar -C <root-dir> -cf - --sort=name <dir> | sha256sum

игнорировать время

Если вас не волнует время доступа или время изменения, также используйте что-то вроде, --mtime='UTC 2019-01-01' чтобы убедиться, что все временные метки одинаковы.

игнорировать право собственности

Обычно нам нужно добавить, --group=0 --owner=0 --numeric-ownerчтобы унифицировать метаданные владельца.

игнорировать некоторые файлы

использовать --exclude=PATTERN

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.