Linux: calculer un seul hachage pour un dossier et un contenu donnés?

95

Il doit sûrement y avoir un moyen de le faire facilement!

J'ai essayé les applications de ligne de commande Linux telles que sha1sumet, md5summais elles ne semblent pouvoir calculer que les hachages de fichiers individuels et afficher une liste de valeurs de hachage, une pour chaque fichier.

J'ai besoin de générer un seul hachage pour tout le contenu d'un dossier (pas seulement les noms de fichiers).

J'aimerais faire quelque chose comme

sha1sum /folder/of/stuff > singlehashvalue

Edit: pour clarifier, mes fichiers sont à plusieurs niveaux dans une arborescence de répertoires, ils ne sont pas tous assis dans le même dossier racine.

Ben L
la source
1
Par «contenu entier», entendez-vous les données logiques de tous les fichiers du répertoire ou ses données avec méta en arrivant à la racine du hachage? Étant donné que les critères de sélection de votre cas d'utilisation sont assez larges, j'ai essayé de traiter quelques critères pratiques dans ma réponse.
six-k

Réponses:

123

Une solution possible serait:

sha1sum chemin / vers / dossier / * | sha1sum

S'il existe toute une arborescence de répertoires, il vaut probablement mieux utiliser find et xargs. Une commande possible serait

trouver chemin / vers / dossier -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

Et, enfin, si vous devez également tenir compte des permissions et des répertoires vides:

(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

Les arguments de le statferont afficher le nom du fichier, suivi de ses autorisations octales. Les deux recherches s'exécuteront l'une après l'autre, provoquant le double de la quantité d'E / S disque, la première trouvant tous les noms de fichiers et la somme de contrôle du contenu, la seconde trouvant tous les noms de fichiers et de répertoires, imprimant le nom et le mode. La liste des "noms de fichiers et sommes de contrôle", suivie de "noms et répertoires, avec permissions" sera alors additionnée, pour une somme de contrôle plus petite.

Vatine
la source
2
et n'oubliez pas de définir LC_ALL = POSIX, afin que les différents outils créent une sortie indépendante des paramètres régionaux.
David Schmitt
2
J'ai trouvé un chat | sha1sum sera considérablement plus rapide que sha1sum | sha1sum. YMMV, essayez chacun de ces éléments sur votre système: time find path / to / folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; time find path / to / folder -type f -print0 | sort -z | xargs -0 chat | sha1sum
Bruno Bronosky
5
@RichardBronosky - Supposons que nous ayons deux fichiers, A et B. A contient "foo" et B contient "bar was here". Avec votre méthode, nous ne pourrions pas séparer cela de deux fichiers C et D, où C contient "foobar" et D contient "was here". En hachant chaque fichier individuellement puis en hachant toutes les paires de "hachage de nom de fichier", nous pouvons voir la différence.
Vatine
2
Pour que cela fonctionne quel que soit le chemin du répertoire (c'est-à-dire lorsque vous souhaitez comparer les hachages de deux dossiers différents), vous devez utiliser un chemin relatif et passer au répertoire approprié, car les chemins sont inclus dans le hachage final:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles
3
@robbles C'est correct et pourquoi je n'ai pas mis d'initiale /sur le path/to/folderbit.
Vatine
25
  • Utilisez un outil de détection d'intrusion dans le système de fichiers comme l' aide .

  • hacher une boule tar du répertoire:

    tar cvf - /path/to/folder | sha1sum

  • Codez quelque chose vous-même, comme oneliner de vatine :

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

David Schmitt
la source
3
+1 pour la solution goudronnée. C'est le plus rapide, mais abandonner le v. Verbosité ne fait que le ralentir.
Bruno Bronosky
6
notez que la solution tar suppose que les fichiers sont dans le même ordre lorsque vous les comparez. Cela dépendra du système de fichiers dans lequel les fichiers résident lors de la comparaison.
nos
5
Le hachage git ne convient pas à cette fin car le contenu du fichier n'est qu'une partie de son entrée. Même pour la validation initiale d'une branche, le hachage est également affecté par le message de validation et les métadonnées de validation, comme l'heure de la validation. Si vous validez la même structure de répertoires plusieurs fois, vous obtiendrez un hachage différent à chaque fois, ainsi le hachage résultant ne convient pas pour déterminer si deux répertoires sont des copies exactes l'un de l'autre en n'envoyant que le hachage.
Zoltan
1
@Zoltan le hachage git est parfaitement bien, si vous utilisez un hachage d'arbre et non un hachage de validation.
hobbs
@hobbs La réponse indiquait à l'origine "commit hash", ce qui n'est certainement pas adapté à cet objectif. Le hachage d'arbre semble être un bien meilleur candidat, mais il pourrait encore y avoir des pièges cachés. L'un qui me vient à l'esprit est que le fait d'avoir le bit exécutable défini sur certains fichiers modifie le hachage de l'arborescence. Vous devez émettre un problème git config --local core.fileMode falseavant de vous engager pour éviter cela. Je ne sais pas s'il y a d'autres mises en garde comme celle-ci.
Zoltan
14

Tu peux faire tar -c /path/to/folder | sha1sum

S.Lott
la source
16
Si vous souhaitez répliquer cette somme de contrôle sur une machine différente, tar n'est peut-être pas un bon choix, car le format semble laisser place à l'ambiguïté et existe dans de nombreuses versions, de sorte que le tar sur une autre machine peut produire une sortie différente à partir des mêmes fichiers.
slowdog
2
préoccupations légitimes de slowdog nonobstant, si vous vous souciez le contenu des fichiers, autorisations, etc. , mais pas la modification du temps, vous pouvez ajouter l' --mtimeoption comme si: tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
Binary Phile
@ S.Lott si la taille du répertoire est grande, je veux dire si la taille du répertoire est si grande, le compresser et obtenir md5 dessus prendra plus de temps
Kasun Siyambalapitiya
13

Si vous voulez juste vérifier si quelque chose dans le dossier a changé, je vous recommande celui-ci:

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

Il vous donnera simplement un hachage de la sortie ls, qui contient les dossiers, les sous-dossiers, leurs fichiers, leur horodatage, leur taille et leurs autorisations. À peu près tout ce dont vous auriez besoin pour déterminer si quelque chose a changé.

Veuillez noter que cette commande ne générera pas de hachage pour chaque fichier, mais c'est pourquoi elle devrait être plus rapide que d'utiliser find.

Shumoapp
la source
1
Je ne sais pas pourquoi cela n'a pas plus de votes positifs étant donné la simplicité de la solution. Quelqu'un peut-il expliquer pourquoi cela ne fonctionnerait pas bien?
Dave C
1
Je suppose que ce n'est pas idéal car le hachage généré sera basé sur le propriétaire du fichier, la configuration du format de date, etc.
Ryota
1
La commande ls peut être personnalisée pour afficher ce que vous voulez. Vous pouvez remplacer -l par -gG pour omettre le groupe et le propriétaire. Et vous pouvez changer le format de la date avec l'option --time-style. Consultez la page de manuel de ls et voyez ce qui répond à vos besoins.
Shumoapp
@DaveC Parce que c'est pratiquement inutile. Si vous souhaitez comparer les noms de fichiers, comparez-les directement. Ils ne sont pas si gros.
Navin
7
@Navin De la question, il n'est pas clair s'il est nécessaire de hacher le contenu d'un fichier ou de détecter un changement dans une arborescence. Chaque étui a ses utilisations. Stocker des noms de fichiers 45K dans une arborescence du noyau, par exemple, est moins pratique qu'un simple hachage. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum fonctionne très bien pour moi
yashma
5

Une approche robuste et propre

  • Tout d'abord, ne monopolisez pas la mémoire disponible ! Hash un fichier en morceaux plutôt que d'alimenter le fichier entier.
  • Différentes approches pour différents besoins / objectifs (tout ce qui suit ou choisissez ce qui s'applique jamais):
    • Hash uniquement le nom d'entrée de toutes les entrées de l'arborescence de répertoires
    • Hash le contenu du fichier de toutes les entrées (en laissant le méta comme, le numéro d'inode, ctime, atime, mtime, size, etc., vous avez l'idée)
    • Pour un lien symbolique, son contenu est le nom référent. Hachez-le ou choisissez de l'ignorer
    • Suivre ou ne pas suivre (nom résolu) le lien symbolique lors du hachage du contenu de l'entrée
    • S'il s'agit d'un répertoire, son contenu n'est que des entrées de répertoire. Lors de la traversée récursive, ils seront éventuellement hachés mais les noms des entrées de répertoire de ce niveau doivent-ils être hachés pour marquer ce répertoire? Utile dans les cas d'utilisation où le hachage est nécessaire pour identifier rapidement un changement sans avoir à parcourir en profondeur pour hacher le contenu. Un exemple serait le changement de nom d'un fichier mais le reste du contenu reste le même et ce sont tous des fichiers assez volumineux
    • Gérez bien les fichiers volumineux (encore une fois, faites attention à la RAM)
    • Gérez des arborescences de répertoires très profondes (faites attention aux descripteurs de fichiers ouverts)
    • Gérer les noms de fichiers non standard
    • Comment procéder avec des fichiers qui sont des sockets, des tubes / FIFO, des périphériques bloc, des périphériques char? Faut-il les hacher aussi?
    • Ne mettez à jour le temps d'accès d'aucune entrée pendant la traversée car ce sera un effet secondaire et contre-productif (intuitif?) Pour certains cas d'utilisation.

C'est ce que j'ai sur la tête, quiconque a passé du temps à travailler là-dessus aurait pratiquement attrapé d'autres pièges et cas de coin.

Voici un outil , très léger sur la mémoire, qui traite la plupart des cas, peut être un peu rugueux sur les bords mais a été très utile.

Un exemple d'utilisation et de sortie de 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

Un extrait de sortie conviviale:

...
... //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
six-k
la source
1
Pouvez-vous donner un bref exemple pour obtenir un sha256 robuste et propre d'un dossier, peut-être pour un dossier Windows avec trois sous-répertoires et quelques fichiers chacun?
Ferit
3

Si vous souhaitez simplement hacher le contenu des fichiers, en ignorant les noms de fichiers, vous pouvez utiliser

cat $FILES | md5sum

Assurez-vous que les fichiers sont dans le même ordre lors du calcul du hachage:

cat $(echo $FILES | sort) | md5sum

Mais vous ne pouvez pas avoir de répertoires dans votre liste de fichiers.


la source
2
Déplacer la fin d'un fichier au début du fichier qui le suit par ordre alphabétique n'affecterait pas le hachage mais devrait le faire. Un délimiteur de fichier ou des longueurs de fichier doivent être inclus dans le hachage.
Jason Stangroome
3

Un autre outil pour y parvenir:

http://md5deep.sourceforge.net/

Tel quel: comme md5sum mais aussi récursif, ainsi que d'autres fonctionnalités.

Jack
la source
1
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
Mamoun Benghezal
3

S'il s'agit d'un référentiel git et que vous souhaitez ignorer tous les fichiers .gitignore, vous pouvez utiliser ceci:

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

Cela fonctionne bien pour moi.

ndbroadbent
la source
Merci beaucoup! :)
visortelle
Pour de nombreuses applications, cette approche est supérieure. Le hachage uniquement des fichiers de code source obtient un hachage suffisamment unique en beaucoup moins de temps.
John McGehee
1

Essayez de le faire en deux étapes:

  1. créer un fichier avec des hachages pour tous les fichiers d'un dossier
  2. hacher ce fichier

Ainsi:

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

Ou faites tout cela en même temps:

# cat `find /folder/of/stuff -type f | sort` | sha1sum
Joao da Silva
la source
for F in 'find ...' ...ne fonctionne pas lorsque vous avez des espaces dans les noms (ce que vous faites toujours de nos jours).
mivk
1

Je dirigerais les résultats pour les fichiers individuels à travers sort(pour éviter une simple réorganisation des fichiers pour changer le hachage) dans md5sumou sha1sum, selon votre choix.

Rafał Dowgird
la source
1

J'ai écrit un script Groovy pour faire ceci:

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)}"

Vous pouvez personnaliser l'utilisation pour éviter d'imprimer chaque fichier, modifier le résumé du message, supprimer le hachage de répertoire, etc. Je l'ai testé par rapport aux données de test NIST et cela fonctionne comme prévu. 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
avoir vérifié
la source
1

J'ai dû vérifier dans un répertoire entier pour les changements de fichiers.

Mais avec l'exclusion, les horodatages, la propriété des répertoires.

Le but est d'obtenir une somme identique n'importe où, si les fichiers sont identiques.

Y compris hébergé dans d'autres machines, indépendamment de tout sauf des fichiers, ou d'une modification de ceux-ci.

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

Il génère une liste de hachage par fichier, puis concatène ces hachages en un seul.

C'est bien plus rapide que la méthode tar.

Pour une plus grande confidentialité dans nos hachages, nous pouvons utiliser sha512sum sur la même recette.

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

Les hachages sont également identiques partout en utilisant sha512sum mais il n'y a aucun moyen connu de l'inverser.

NVRM
la source
Cela semble beaucoup plus simple que la réponse acceptée pour le hachage d'un répertoire. Je ne trouvais pas la réponse acceptée fiable. Un problème ... y a-t-il une chance que les hachages sortent dans un ordre différent? sha256sum /tmp/thd-agent/* | sortest ce que j'essaye pour une commande fiable, puis juste hacher cela.
thinktt le
Salut, on dirait que les hachages sont classés par défaut par ordre alphabétique. Qu'entendez-vous par commande fiable? Vous devez organiser tout cela par vous-même. Par exemple en utilisant des tableaux associatifs, entrée + hachage. Ensuite, vous triez ce tableau par entrée, cela donne une liste de hachages calculés dans l'ordre de tri. Je crois que vous pouvez utiliser un objet json autrement, et hacher tout l'objet directement.
NVRM
Si je comprends bien, vous dites qu'il hache les fichiers par ordre alphabétique. Cela semble juste. Quelque chose dans la réponse acceptée ci-dessus me donnait parfois des ordres différents intermittents, alors j'essaie simplement de m'assurer que cela ne se reproduise plus. Je vais m'en tenir à mettre le tri à la fin. Semble fonctionner. Le seul problème avec cette méthode par rapport à la réponse acceptée que je vois est qu'elle ne traite pas des dossiers imbriqués. Dans mon cas, je n'ai aucun dossier, donc cela fonctionne très bien.
thinktt le
et quoi ls -r | sha256sum?
NVRM
@NVRM l'a essayé et il a juste vérifié les changements de nom de fichier, pas le contenu du fichier
Gi0rgi0s
0

Vous pouvez sha1sumgénérer la liste des valeurs de hachage, puis à sha1sumnouveau cette liste, cela dépend exactement de ce que vous voulez accomplir.

Ronny Vindenes
la source
0

Voici une variante simple et courte de Python 3 qui fonctionne bien pour les fichiers de petite taille (par exemple, une arborescence source ou quelque chose, où chaque fichier individuellement peut facilement s'intégrer dans la RAM), en ignorant les répertoires vides, en fonction des idées des autres solutions:

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()                          

Cela fonctionne comme ceci:

  1. Trouvez tous les fichiers du répertoire de manière récursive et triez-les par nom
  2. Calculer le hachage (par défaut: SHA-1) de chaque fichier (lit le fichier entier en mémoire)
  3. Créer un index textuel avec des lignes "filename = hash"
  4. Recodez cet index dans une chaîne d'octets UTF-8 et hachez

Vous pouvez passer une fonction de hachage différente comme deuxième paramètre si SHA-1 n'est pas votre tasse de thé.

Thomas Perl
la source