Linux: обчислити один хеш для даної папки та вмісту?


98

Напевно, повинен бути спосіб зробити це легко!

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

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

Я хотів би зробити щось подібне

sha1sum /folder/of/stuff > singlehashvalue

Редагувати: для уточнення, мої файли знаходяться на декількох рівнях у дереві каталогів, вони не всі сидять в одній кореневій папці.


1
Під поняттям "весь вміст" ви маєте на увазі логічні дані всіх файлів у каталозі або його дані разом із мета, коли надходять до кореневого хешу? Оскільки критерії відбору у вашому випадку використання досить широкі, у своїй відповіді я спробував розглянути декілька практичних.
six-k

Відповіді:


124

Одним із можливих способів є:

шлях sha1sum / до / папки / * | sha1sum

Якщо існує ціле дерево каталогів, вам, мабуть, краще використовувати find та xargs. Однією з можливих команд було б

знайти шлях / до / папки -типу f -print0 | сортувати -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, спробуйте кожен з них у вашій системі: час знайти шлях / до / папки -типу f -print0 | сортувати -z | xargs -0 sha1sum | ша1сум; час знайти шлях / до / папки -типу f -print0 | сортувати -z | xargs -0 кішка | 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

25
  • Використовуйте інструмент виявлення вторгнень файлової системи, як помічник .

  • хеш кульки tar у каталозі:

    tar cvf - /path/to/folder | sha1sum

  • Закодуйте щось самостійно, наприклад, oneliner Ватіна :

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


3
+1 для розчину дьогтю. Це найшвидше, але падіння v. Багатослів'я лише уповільнює його.
Бруно Броноскі

7
зауважте, що розв’язання tar передбачає, що файли мають однаковий порядок при їх порівнянні. Чи будуть вони залежати від файлової системи, в якій знаходяться файли при порівнянні.
nos

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 на нього займе більше часу
Kasun Siyambalapitiya

13

Якщо ви просто хочете перевірити, чи щось у папці не змінилося, я рекомендую це:

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

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

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


1
Я не впевнений, чому це не має більше голосів, враховуючи простоту рішення. Хто-небудь може пояснити, чому це не буде працювати добре?
Dave C

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

1
Команду ls можна налаштувати, щоб виводити все, що ти хочеш. Ви можете замінити -l на -gG, щоб опустити групу та власника. І ви можете змінити формат дати за допомогою параметра --time-style. В основному загляньте на сторінку довідкової служби ls і перевірте, що відповідає вашим потребам.
Шумоапп,

@DaveC Бо це майже марно. Якщо ви хочете порівняти імена файлів, просто порівняйте їх безпосередньо. Вони не такі великі.
Navin

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

5

Надійний та чистий підхід

  • Перш за все, не кажіть доступну пам’ять ! Хешуйте файл шматками, а не подавайте весь файл.
  • Різні підходи для різних потреб / цілей (все наведене нижче або виберіть, що колись застосовується):
    • Хеш лише ім'я запису всіх записів у дереві каталогів
    • Хешуйте вміст файлу всіх записів (залишаючи мета, наприклад, номер inode, ctime, atime, mtime, розмір тощо, ви зрозуміли)
    • Для символічного посилання його змістом є референтна назва. Змістіть його в хеші або виберіть пропустити
    • Дотримуйтесь чи не дотримуйтесь (вирішене ім'я) символічного посилання під час хешування вмісту запису
    • Якщо це каталог, його вміст - це просто записи в каталозі. Під час рекурсивного обходу вони врешті-решт будуть хешовані, але чи слід хешувати назви записів каталогу цього рівня для позначення цього каталогу? Це корисно у випадках використання, коли хеш потрібен для швидкого виявлення змін без необхідності глибокого переходу до хешування вмісту. Прикладом можуть бути зміни імені файлу, але решта вмісту залишається незмінним, і всі вони є досить великими файлами
    • Добре обробляти великі файли (знову ж, майте на увазі оперативну пам’ять)
    • Обробляти дуже глибокі дерева каталогів (пам’ятайте про дескриптори відкритих файлів)
    • Обробляйте нестандартні імена файлів
    • Як діяти з файлами, які є сокетами, трубопроводами / FIFO, блокуючими пристроями, пристроями char? Повинні їх також хешувати?
    • Не оновлюйте час доступу до будь-якого входу під час обходу, оскільки це буде побічним ефектом та контрпродуктивно (інтуїтивно?) Для певних випадків використання.

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

Ось інструмент , дуже легкий для пам'яті, який розглядає більшість випадків, може бути трохи грубим по краях, але був дуже корисним.

Приклад використання та виводу 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. Складіть текстовий індекс з рядками "ім'я файлу = хеш"
  4. Кодуйте цей індекс назад у байтовий рядок UTF-8 і хешуйте його

Ви можете передати іншу хеш-функцію як другий параметр, якщо SHA-1 не є вашою чашкою чаю.

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