Как я могу вычислить хэш для каталога файловой системы с помощью Python?
Я использую этот код для вычисления значения хэша для файла:
m = hashlib.md5()
with open("calculator.pdf", 'rb') as fh:
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
hash_value = m.hexdigest()
print hash_value
когда я попробовал его в папке "folder", я получил
IOError: [Errno 13] Permission denied: folder
Как я могу вычислить хэш-значение для папки?
Ответы
Ответ 1
Этот Recipe обеспечивает хорошую функцию, чтобы делать то, что вы просите. Я изменил его, чтобы использовать хэш MD5 вместо SHA1, поскольку ваш исходный вопрос спрашивает
def GetHashofDirs(directory, verbose=0):
import hashlib, os
SHAhash = hashlib.md5()
if not os.path.exists (directory):
return -1
try:
for root, dirs, files in os.walk(directory):
for names in files:
if verbose == 1:
print 'Hashing', names
filepath = os.path.join(root,names)
try:
f1 = open(filepath, 'rb')
except:
# You can't open the file for some reason
f1.close()
continue
while 1:
# Read file in as little chunks
buf = f1.read(4096)
if not buf : break
SHAhash.update(hashlib.md5(buf).hexdigest())
f1.close()
except:
import traceback
# Print the stack traceback
traceback.print_exc()
return -2
return SHAhash.hexdigest()
Вы можете использовать его следующим образом:
print GetHashofDirs('folder_to_hash', 1)
Результат выглядит следующим образом: хеширует каждый файл:
...
Hashing file1.cache
Hashing text.txt
Hashing library.dll
Hashing vsfile.pdb
Hashing prog.cs
5be45c5a67810b53146eaddcae08a809
Возвращаемое значение этого вызова функции возвращается как хэш. В этом случае 5be45c5a67810b53146eaddcae08a809
Ответ 2
Использовать пакет checkumdir python для вычисления контрольной суммы/хеша каталога. Он доступен в https://pypi.python.org/pypi/checksumdir/1.0.5
Использование:
import checksumdir
hash = checksumdir.dirhash("c:\\temp")
print hash
Ответ 3
Я не фанат того, как рецепт, указанный в ответе, был написан. У меня есть гораздо более простая версия, которую я использую:
import hashlib
import os
def hash_directory(path):
digest = hashlib.sha1()
for root, dirs, files in os.walk(path):
for names in files:
file_path = os.path.join(root, names)
# Hash the path and add to the digest to account for empty files/directories
digest.update(hashlib.sha1(file_path[len(path):].encode()).digest())
# Per @pt12lol - if the goal is uniqueness over repeatability, this is an alternative method using 'hash'
# digest.update(str(hash(file_path[len(path):])).encode())
if os.path.isfile(file_path):
with open(file_path, 'rb') as f_obj:
while True:
buf = f_obj.read(1024 * 1024)
if not buf:
break
digest.update(buf)
return digest.hexdigest()
Я обнаружил, что исключения обычно генерируются всякий раз, когда встречается что-то вроде alias
(отображается в os.walk()
, но вы не можете открыть его напрямую). os.path.isfile()
эти проблемы.
Если в каталоге должен быть реальный файл, который я пытаюсь хэшировать, и он не может быть открыт, пропуск этого файла и продолжение не является хорошим решением. Это влияет на результат хэша. Лучше вообще убить попытку хеширования. Здесь оператор try
будет обернут вокруг вызова моей функции hash_directory()
.
>>> try:
... print(hash_directory('/tmp'))
... except:
... print('Failed!')
...
e2a075b113239c8a25c7e1e43f21e8f2f6762094
>>>
Ответ 4
Я продолжаю видеть, что этот код распространяется через различные форумы.
ответ рецепта ActiveState работает, но, как отметил Антонио, он не может быть повторяемым в файловых системах из-за невозможности представить файлы в тот же порядок (попробуйте). Одним из исправлений является изменение
for root, dirs, files in os.walk(directory):
for names in files:
к
for root, dirs, files in os.walk(directory):
for names in sorted(files):
(Да, я ленив здесь. Это сортирует только имена файлов, а не каталоги. Этот же принцип применяется)
Ответ 5
Вот реализация, которая использует pathlib.Path вместо того, чтобы полагаться на os.walk. Он сортирует содержимое каталога перед итерацией, поэтому он должен повторяться на нескольких платформах. Он также обновляет хэш с именами файлов/каталогов, поэтому добавление пустых файлов и каталогов изменит хэш.
Версия с аннотациями типов (Python 3.6 или выше):
import hashlib
from _hashlib import HASH as Hash
from pathlib import Path
from typing import Union
def md5_update_from_file(filename: Union[str, Path], hash: Hash) -> Hash:
assert Path(filename).is_file()
with open(str(filename), "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash.update(chunk)
return hash
def md5_file(filename: Union[str, Path]) -> str:
return str(md5_update_from_file(filename, hashlib.md5()).hexdigest())
def md5_update_from_dir(directory: Union[str, Path], hash: Hash) -> Hash:
assert Path(directory).is_dir()
for path in sorted(Path(directory).iterdir()):
hash.update(path.name.encode())
if path.is_file():
hash = md5_update_from_file(path, hash)
elif path.is_dir():
hash = md5_update_from_dir(path, hash)
return hash
def md5_dir(directory: Union[str, Path]) -> str:
return str(md5_update_from_dir(directory, hashlib.md5()).hexdigest())
Без аннотации типов:
import hashlib
from pathlib import Path
def md5_update_from_file(filename, hash):
assert Path(filename).is_file()
with open(str(filename), "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash.update(chunk)
return hash
def md5_file(filename):
return md5_update_from_file(filename, hashlib.md5()).hexdigest()
def md5_update_from_dir(directory, hash):
assert Path(directory).is_dir()
for path in sorted(Path(directory).iterdir()):
hash.update(path.name.encode())
if path.is_file():
hash = md5_update_from_file(path, hash)
elif path.is_dir():
hash = md5_update_from_dir(path, hash)
return hash
def md5_dir(directory):
return md5_update_from_dir(directory, hashlib.md5()).hexdigest()
Сжатая версия, если вам нужно только хэшировать каталоги:
def md5_update_from_dir(directory, hash):
assert Path(directory).is_dir()
for path in sorted(Path(directory).iterdir()):
hash.update(path.name.encode())
if path.is_file():
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash.update(chunk)
elif path.is_dir():
hash = md5_update_from_dir(path, hash)
return hash
def md5_dir(directory):
return md5_update_from_dir(directory, hashlib.md5()).hexdigest()
Использование: md5_hash = md5_dir("/some/directory")
Ответ 6
Я оптимизировал дальнейший ответ Энди.
Ниже приведена реализация python3, а не python2. Он использует SHA1, обрабатывает некоторые случаи, когда необходимо кодирование, помечен и содержит несколько доктрин.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""dir_hash: Return SHA1 hash of a directory.
- Copyright (c) 2009 Stephen Akiki, 2018 Joe Flack
- MIT License (http://www.opensource.org/licenses/mit-license.php)
- http://akiscode.com/articles/sha-1directoryhash.shtml
"""
import hashlib
import os
def update_hash(running_hash, filepath, encoding=''):
"""Update running SHA1 hash, factoring in hash of given file.
Side Effects:
running_hash.update()
"""
if encoding:
file = open(filepath, 'r', encoding=encoding)
for line in file:
hashed_line = hashlib.sha1(line.encode(encoding))
hex_digest = hashed_line.hexdigest().encode(encoding)
running_hash.update(hex_digest)
file.close()
else:
file = open(filepath, 'rb')
while True:
# Read file in as little chunks.
buffer = file.read(4096)
if not buffer:
break
running_hash.update(hashlib.sha1(buffer).hexdigest())
file.close()
def dir_hash(directory, verbose=False):
"""Return SHA1 hash of a directory.
Args:
directory (string): Path to a directory.
verbose (bool): If True, prints progress updates.
Raises:
FileNotFoundError: If directory provided does not exist.
Returns:
string: SHA1 hash hexdigest of a directory.
"""
sha_hash = hashlib.sha1()
if not os.path.exists(directory):
raise FileNotFoundError
for root, dirs, files in os.walk(directory):
for names in files:
if verbose:
print('Hashing', names)
filepath = os.path.join(root, names)
try:
update_hash(running_hash=sha_hash,
filepath=filepath)
except TypeError:
update_hash(running_hash=sha_hash,
filepath=filepath,
encoding='utf-8')
return sha_hash.hexdigest()