Le programme md5sum ne fournit pas de sommes de contrôle pour les répertoires. Je veux obtenir une seule somme de contrôle MD5 pour tout le contenu d'un répertoire, y compris les fichiers dans les sous-répertoires. Autrement dit, une somme de contrôle combinée constituée de tous les fichiers. Y a-t-il un moyen de faire cela?
La bonne façon dépend exactement de la raison pour laquelle vous demandez:
Si vous avez juste besoin d'un hachage du contenu du fichier de l'arborescence, cela fera l'affaire:
$ find -s somedir -type f -exec md5sum {} \; | md5sum
Cela résume d'abord tout le contenu du fichier individuellement, dans un ordre prévisible, puis passe cette liste de noms de fichiers et de hachages MD5 à hacher elle-même, donnant une valeur unique qui ne change que lorsque le contenu de l'un des fichiers de l'arborescence change.
Malheureusement, find -s
ne fonctionne qu'avec BSD find (1), utilisé sous macOS, FreeBSD, NetBSD et OpenBSD. Pour obtenir quelque chose de comparable sur un système avec GNU ou SUS find (1), vous avez besoin de quelque chose d'un peu plus laid:
$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum
Nous avons imité le comportement de BSD find -s
en ajoutant un appel à sort
. Le -k 2
bit lui dit de sauter le hachage MD5, donc il ne trie que les noms de fichiers, qui sont dans le champ 2 jusqu'à la fin de la ligne par le calcul de sort
.
Il y a une faiblesse avec cette version de la commande, c'est qu'elle risque de devenir confuse si vous avez des noms de fichiers avec des retours à la ligne, car cela ressemblera à plusieurs lignes à l'appel sort
. Le find -s
la variante n'a pas ce problème, car la traversée et le tri de l'arborescence ont lieu dans le même programme, find
.
Dans les deux cas, le tri est nécessaire pour éviter les faux positifs: les systèmes de fichiers Unix/Linux les plus courants ne maintiennent pas les listes de répertoires dans un ordre stable et prévisible. Vous pourriez ne pas vous en rendre compte en utilisant ls
et autres, qui trient silencieusement le contenu du répertoire pour vous. L'appel de find
sans trier sa sortie d'une manière ou d'une autre entraînera l'ordre des lignes dans la sortie pour correspondre à l'ordre dans lequel le système de fichiers sous-jacent les renvoie, ce qui fera que cette commande donnera une valeur de hachage modifiée si l'ordre des fichiers donné à mesure que l'entrée change, même si les données restent identiques.
Vous pouvez vous demander si le -k 2
bit dans la commande GNU sort
ci-dessus est nécessaire. Étant donné que le hachage des données du fichier est un proxy adéquat pour le nom du fichier tant que le contenu n'a pas changé , nous n'obtiendrons pas de faux positifs si nous laissons tomber cette option, ce qui nous permet d'utiliser la même commande avec les deux GNU et BSD sort
. Cependant, réalisez qu'il y a une petite chance (1: 2128 avec MD5) que l'ordre exact des noms de fichiers ne correspond pas à l'ordre partiel que faire sans -k 2
peut donner en cas de collision de hachage. Gardez à l'esprit, cependant, si de si petites chances de discordance importent à votre application, cette approche est probablement hors de question pour vous.
Vous devrez peut-être modifier le md5sum
commandes à md5
ou une autre fonction de hachage. Si vous choisissez une autre fonction de hachage et avez besoin de la deuxième forme de la commande pour votre système, vous devrez peut-être ajuster la commande sort
en conséquence. Un autre piège est que certains programmes de sommation de données n'écrivent pas du tout un nom de fichier, un excellent exemple étant l'ancien programme Unix sum
.
Cette méthode est quelque peu inefficace, appelant md5sum
N + 1 fois, où N est le nombre de fichiers dans l'arborescence, mais c'est un coût nécessaire pour éviter de hacher les métadonnées de fichier et de répertoire.
Si vous devez être en mesure de détecter que quelque chose dans une arborescence a changé, pas seulement le contenu du fichier, demandez à tar
d'emballer le répertoire contenu pour vous, puis envoyez-le à md5sum
:
$ tar -cf - somedir | md5sum
Étant donné que tar
voit également les autorisations de fichiers, la propriété, etc., cela détectera également les modifications de ces éléments, pas seulement les modifications du contenu des fichiers.
Cette méthode est considérablement plus rapide, car elle ne fait qu'un seul passage sur l'arborescence et n'exécute le programme de hachage qu'une seule fois.
Comme avec la méthode basée sur find
ci-dessus, tar
va traiter les noms de fichiers dans l'ordre dans lequel le système de fichiers sous-jacent les renvoie. Il se pourrait bien que dans votre application, vous soyez sûr que cela ne se produira pas. Je peux penser à au moins trois modèles d'utilisation différents où cela est susceptible d'être le cas. (Je ne vais pas les énumérer, car nous entrons dans un territoire de comportement non spécifié. Chaque système de fichiers peut être différent ici, même d'une version de l'OS à la suivante.)
Si vous vous retrouvez avec des faux positifs, je vous recommande d'utiliser le find | cpio
option dans réponse de Gilles .
La somme de contrôle doit être d'une représentation déterministe et non ambiguë des fichiers sous forme de chaîne. Déterministe signifie que si vous placez les mêmes fichiers aux mêmes emplacements, vous obtiendrez le même résultat. Sans ambiguïté signifie que deux ensembles de fichiers différents ont des représentations différentes.
Faire une archive contenant les fichiers est un bon début. Il s'agit d'une représentation non ambiguë (évidemment, puisque vous pouvez récupérer les fichiers en extrayant l'archive). Il peut inclure des métadonnées de fichier telles que les dates et la propriété. Cependant, ce n'est pas encore tout à fait vrai: une archive est ambiguë, car sa représentation dépend de l'ordre dans lequel les fichiers sont stockés et, le cas échéant, de la compression.
Une solution consiste à trier les noms de fichiers avant de les archiver. Si les noms de vos fichiers ne contiennent pas de retours à la ligne, vous pouvez exécuter find | sort
pour les répertorier et les ajouter aux archives dans cet ordre. Prenez soin de dire à l'archiveur de ne pas rentrer dans les répertoires. Voici des exemples avec POSIX pax
, GNU tar et 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
Si vous souhaitez uniquement prendre en compte les données du fichier et non les métadonnées, vous pouvez créer une archive qui inclut uniquement le contenu du fichier, mais il n'existe aucun outil standard pour cela. Au lieu d'inclure le contenu du fichier, vous pouvez inclure le hachage des fichiers. Si les noms de fichiers ne contiennent pas de sauts de ligne et qu'il n'y a que des fichiers et répertoires normaux (pas de liens symboliques ou de fichiers spéciaux), c'est assez facile, mais vous devez prendre soin de quelques choses:
{ 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
Nous incluons une liste de répertoires en plus de la liste des sommes de contrôle, sinon les répertoires vides seraient invisibles. La liste des fichiers est triée (dans un environnement local spécifique et reproductible - merci à Peter.O de me le rappeler). echo
sépare les deux parties (sans cela, vous pourriez créer des répertoires vides dont le nom ressemble à md5sum
sortie qui pourrait également passer pour les fichiers ordinaires). Nous incluons également une liste des tailles de fichiers, pour éviter attaques par extension de longueur .
Soit dit en passant, MD5 est obsolète. S'il est disponible, envisagez d'utiliser SHA-2, ou au moins SHA-1.
Voici une variante du code ci-dessus qui repose sur les outils GNU pour séparer les noms de fichier avec des octets nuls. Cela permet aux noms de fichier de contenir des retours à la ligne. Le GNU Les utilitaires digest citent des caractères spéciaux dans leur sortie, il n'y aura donc pas de nouvelles lignes ambiguës.
{ 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
Voici un script Python qui teste de manière minimale qui construit une table de hachage décrivant une hiérarchie de fichiers. Il prend en compte les répertoires et le contenu des fichiers et ignore les liens symboliques et autres fichiers, et renvoie une erreur fatale si un fichier peut pas lu.
#! /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()
Jetez un oeil à md5deep . Certaines des fonctionnalités de md5deep qui peuvent vous intéresser:
Opération récursive - md5deep est capable d'examiner récursivement une arborescence de répertoires entière. Autrement dit, calculez le MD5 pour chaque fichier d'un répertoire et pour chaque fichier de chaque sous-répertoire.
Mode de comparaison - md5deep peut accepter une liste de hachages connus et les comparer à un ensemble de fichiers d'entrée. Le programme peut afficher soit les fichiers d'entrée qui correspondent à la liste des hachages connus, soit ceux qui ne correspondent pas.
...
Si votre objectif est simplement de trouver des différences entre deux répertoires, envisagez d'utiliser diff.
Essaye ça:
diff -qr dir1 dir2
Vous pouvez hacher chaque fichier récursivement puis hacher le texte résultant:
> md5deep -r -l . | sort | md5sum
d43417958e47758c6405b5098f151074 *-
md5deep est requis.
solution :
$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad
fonctionne rapidement et plus facile solution puis bash scripting.
Pour faire suite à cette excellente réponse , si vous souhaitez accélérer le calcul de la somme de contrôle pour un grand répertoire, essayez GNU Parallel :
find -s somedir -type f | parallel -k -n 100 md5 {} | md5
(Il s'agit d'un Mac avec md5
, remplacez si nécessaire.)
Le -k
L'indicateur est important, il demande à parallel
de maintenir l'ordre, sinon la somme globale peut changer run pour s'exécuter même si les fichiers sont tous identiques. -n 100
dit d'exécuter chaque instance de md5
avec 100 arguments, c'est un paramètre que vous pouvez Tweak pour le meilleur temps d'exécution. Voir également -X
drapeau de parallel
(mais dans mon cas personnel, cela a causé une erreur.)
J'utilise cet extrait pour volumes modérés :
find . -xdev -type f -print0 | LC_COLLATE=C sort -z | xargs -0 cat | md5sum -
et celui-ci pour XXXL :
find . -xdev -type f -print0 | LC_COLLATE=C sort -z | xargs -0 tail -qc100 | md5sum -
J'avais besoin d'une version qui ne vérifiait que les noms de fichiers car le contenu résidait dans différents répertoires.
Cette version (la réponse de Warren Young) m'a beaucoup aidé, mais ma version de md5sum
renvoie le nom du fichier (par rapport au chemin à partir duquel j'ai exécuté la commande), et les noms de dossier étaient différents, donc même si les sommes de contrôle des fichiers individuels correspondaient, la somme de contrôle finale non.
Pour corriger cela, dans mon cas, j'avais juste besoin de supprimer le nom de fichier de chaque ligne de la sortie find
(sélectionnez uniquement le premier mot séparé par des espaces en utilisant cut
):
find -s somedir -type f -exec md5sum {} \; | cut -d" " -f1 | md5sum
nix-hash
de le gestionnaire de paquets Nix
La commande nix-hash calcule le hachage cryptographique du contenu de chaque chemin et l'imprime sur la sortie standard. Par défaut, il calcule un hachage MD5, mais d'autres algorithmes de hachage sont également disponibles. Le hachage est imprimé en hexadécimal.
Le hachage est calculé sur une sérialisation de chaque chemin: un vidage de l'arborescence du système de fichiers enracinée sur le chemin. Cela permet de hacher les répertoires et les liens symboliques ainsi que les fichiers normaux. Le vidage est au format NAR produit par nix-store --dump. Ainsi, le chemin nix-hash donne le même hachage cryptographique que le chemin nix-store --dump | md5sum.
Une bonne somme de contrôle d'arbre est l'identifiant d'arbre de Git.
Il n'y a malheureusement pas d'outil autonome disponible pour le faire (du moins je ne le sais pas), mais si vous avez Git à portée de main, vous pouvez simplement faire semblant de configurer un nouveau référentiel et ajouter les fichiers que vous souhaitez vérifier à l'index.
Cela vous permet de produire le hachage d'arbre (reproductible) - qui comprend uniquement le contenu, les noms de fichier et certains modes de fichier réduits (exécutable).
Je ne voulais pas de nouveaux exécutables ni de solutions maladroites alors voici mon point de vue:
#!/bin/sh
# md5dir.sh by Camilo Martin, 2014-10-01.
# Give this a parameter and it will calculate an md5 of the directory's contents.
# It only takes into account file contents and paths relative to the directory's root.
# This means that two dirs with different names and locations can hash equally.
if [[ ! -d "$1" ]]; then
echo "Usage: md5dir.sh <dir_name>"
exit
fi
d="$(tr '\\' / <<< "$1" | tr -s / | sed 's-/$--')"
c=$((${#d} + 35))
find "$d" -type f -exec md5sum {} \; | cut -c 1-33,$c- | sort | md5sum | cut -c 1-32
Un script qui est bien testé et prend en charge un certain nombre d'opérations, notamment la recherche de doublons, la comparaison de données et de métadonnées, l'affichage d'ajouts ainsi que les modifications et les suppressions, vous aimerez peut-être Fingerprint .
L'empreinte digitale en ce moment ne produit pas une seule somme de contrôle pour un répertoire, mais un fichier de transcription qui inclut des sommes de contrôle pour tous les fichiers de ce répertoire.
fingerprint analyze
Cela générera index.fingerprint
dans le répertoire courant qui inclut les sommes de contrôle, les noms de fichiers et les tailles de fichiers. Par défaut, il utilise à la fois MD5
et SHA1.256
.
À l'avenir, j'espère ajouter la prise en charge de Merkle Trees dans Fingerprint, ce qui vous donnera une seule somme de contrôle de niveau supérieur. À l'heure actuelle, vous devez conserver ce fichier pour effectuer la vérification.
Bon mot:
find directory -exec md5sum {} \; 2>&1 | sort -k 2 | md5sum
Cela répertorie tous les fichiers et répertoires et obtient md5sum
pour chaque. Puis obtient md5sum
pour tout.
Tricky bit résolu ici que md5sum
n'est pas capable de faire la somme pour un répertoire, mais il nous le dit: md5sum: dir/sub_dir: Is a directory
. Nous déplaçons simplement ce message vers une sortie standard.
Faire individuellement pour tous les fichiers de chaque répertoire.
# Calculating
find dir1 | xargs md5sum > dir1.md5
find dir2 | xargs md5sum > dir2.md5
# Comparing (and showing the difference)
paste <(sort -k2 dir1.md5) <(sort -k2 dir2.md5) | awk '$1 != $3'
Cette réponse est destinée à être une mise à jour supplémentaire de l'approche de l'utilisation de la sortie Tar pour hacher le contenu des répertoires, comme cela a été proposé (entre autres) dans les excellentes réponses de Warren Young et Gilles il y a quelque temps.
Depuis lors, au moins openSUSE (depuis sa version 12.2) a changé son format par défaut GNU Tar de "GNU tar 1.13.x format" au format (légèrement) supérieur "POSIX 1003.1-2001 (pax)" . Également en amont (parmi les développeurs de GNU Tar) dont ils discutent pour effectuer la même migration, voir par exemple le dernier paragraphe sur cette page du manuel GNU Tar :
Le format par défaut pour GNU tar est défini au moment de la compilation. Vous pouvez le vérifier en exécutant
tar --help
Et en examinant les dernières lignes de sa sortie. Habituellement, GNU tar est configuré pour créer des archives au formatgnu
, cependant, la future version passera àposix
.
(Cette page donne également un bon aperçu des différents formats d'archives disponibles avec GNU Tar.)
Dans notre cas, où nous tarons le contenu du répertoire et hachons le résultat, et sans prendre de mesures spécifiques, un changement de GNU au format POSIX a les conséquences suivantes:
Malgré un contenu de répertoire identique, la somme de contrôle résultante sera différente.
Malgré un contenu de répertoire identique, la somme de contrôle résultante sera différente d'une exécution à l'autre si les en-têtes pax par défaut sont utilisés.
Ce dernier vient du fait que le format POSIX (pax) comprend des en-têtes pax étendus qui sont déterminés par une chaîne de formatage par défaut %d/PaxHeaders.%p/%f
Dans GNU Tar. Dans cette chaîne) , le spécificateur %p
est remplacé par l'ID de processus du processus Tar générateur, qui est bien sûr différent d'une exécution à l'autre. Voir cette section de la GNU Tar manual et en particulier celui-ci pour plus de détails.
À l'heure actuelle, datant du 28/03/2019, il y a un commit accepté en amont qui désamorce ce problème.
Donc, pour pouvoir continuer à utiliser GNU Tar dans le cas d'utilisation donné, je peux recommander les options alternatives suivantes:
Utilisez l'option Tar --format=gnu
Pour dire explicitement à Tar de générer l'archive dans le "vieux" format. Ceci est obligatoire pour valider les "anciennes" sommes de contrôle.
Utilisez le nouveau format POSIX, mais spécifiez explicitement un en-tête pax approprié, par exemple par --pax-option="exthdr.name=%d/PaxHeaders/%f"
. Cependant, cela rompt la compatibilité descendante avec les "anciens" sommes de contrôle.
Voici un fragment de code Bash que j'utilise régulièrement pour calculer les sommes de contrôle du contenu du répertoire, y compris les métadonnées:
( export LC_ALL=C
find <paths> ! -type s -print0 |
sort -z |
tar cp --format=gnu --numeric-owner \
--atime-preserve \
--no-recursion --null --files-from - |
md5sum --binary; )
Ici, <paths>
Est remplacé par une liste séparée par des espaces des chemins de tous les répertoires que je souhaite voir couverts par la somme de contrôle. Le but de l'utilisation des paramètres régionaux C, de la séparation des octets nuls des noms de fichiers et de l'utilisation de la recherche et du tri pour obtenir un ordre indépendant du système de fichiers des fichiers dans l'archive est déjà suffisamment discuté dans d'autres réponses.
Les parenthèses environnantes gardent le paramètre LC_ALL
Local dans un sous-shell.
De plus, j'utilise l'expression ! -type s
Avec find
pour éviter les avertissements de Tar qui se produisent si les fichiers socket font partie du contenu du répertoire: GNU Tar ne fait pas archive sockets. Si vous préférez être informé des sockets ignorés, laissez cette expression de côté.
J'utilise --numeric-owner
Avec Tar, pour pouvoir vérifier les sommes de contrôle plus tard, même sur les systèmes, où tous les propriétaires de fichiers ne sont pas connus.
L'option --atime-preserve
Pour Tar est mieux omise si l'un des <paths>
Se trouve sur un périphérique monté en lecture seule. Sinon, vous serez averti pour chaque fichier dont l'horodatage d'accès Tar n'a pas pu être restauré. Pour l'écriture activée <paths>
, J'utilise cette option, eh bien, pour conserver les horodatages d'accès dans les répertoires hachés.
L'option Tar --no-recursion
, Qui était déjà utilisée dans proposition Gilles , empêche Tar de descendre de façon récursive dans les répertoires par lui-même, et d'opérer à la place fichier par fichier sur tout ce qui est alimenté par le trié find
sortie.
Et enfin, ce n'est pas vrai que j'utilise md5sum
: J'utilise en fait sha256sum
.
J'utilise l'approche suivante pour déterminer si le contenu/les attributs du répertoire ont changé:
cd /path/to/dir; ls -lnAR --time-style=+%s . | md5sum
Si vous souhaitez également suivre les modifications des attributs étendus:
cd /path/to/dir; (ls -lnAR --time-style=+%s .; getfacl -Rns .) | md5sum
Dans des circonstances normales, il n'est pas nécessaire de vérifier le contenu des fichiers car l'heure de modification du fichier indique déjà une modification du contenu du fichier. Cela accélère également toute l'opération.
Remarques:
Si vous souhaitez également vérifier le contenu des fichiers: tar c -C /path/to/dir | md5sum
Ou avec des attributs étendus: cd /path/to/dir; (tar c .; getfacl -Rns .) | md5sum
Afin de le faire fonctionner sur une plus large gamme de systèmes, j'ai dû définir les paramètres régionaux et filtrer également le nombre de liens fixes et les tailles de répertoire de la sortie ls avec awk:
cd /path;LC_ALL=C ls -lnAR --time-style=+%s .|awk '{$2=0;$1~/^d/&&$5=0;print}'|md5sum
C'est ce que j'ai sur ma tête, quiconque a passé un peu de temps à travailler sur ce sujet aurait attrapé d'autres pièges et cas de coin.
Voici un outil (avertissement: j'y contribue) dtreetrawl , très léger sur la mémoire, qui traite la plupart des cas, peut être un peu rude sur les bords mais a été très utile.
Usage: dtreetrawl [OPTION...] "/trawl/me" [path2,...] Help Options: -h, --help Show help options Application Options: -t, --terse Produce a terse output; parsable. -d, --delim=: Character or string delimiter/separator for terse output(default ':') -l, --max-level=N Do not traverse tree beyond N level(s) --hash Hash the files to produce checksums(default is MD5). -c, --checksum=md5 Valid hashing algorithms: md5, sha1, sha256, sha512. -s, --hash-symlink Include symbolic links' referent name while calculating the root checksum -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
Un exemple de sortie conviviale pour les humains:
... ... //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
Si vous n'avez pas besoin de md5, vous pouvez essayer
find . -type f | xargs cksum | cksum