
Das Programm md5sum stellt keine Prüfsummen für Verzeichnisse bereit. Ich möchte eine einzelne MD5-Prüfsumme für den gesamten Inhalt eines Verzeichnisses erhalten, einschließlich der Dateien in den Unterverzeichnissen. Das heißt, eine kombinierte Prüfsumme aus allen Dateien. Gibt es eine Möglichkeit, dies zu tun?
Antwort1
Die richtige Vorgehensweise hängt davon ab, warum Sie genau fragen:
Option 1: Nur Daten vergleichen
Wenn Sie lediglich einen Hash des Dateiinhalts des Baums benötigen, reicht das Folgende aus:
$ find -s somedir -type f -exec md5sum {} \; | md5sum
Dabei werden zunächst alle Dateiinhalte einzeln und in vorhersehbarer Reihenfolge zusammengefasst. Anschließend werden die Liste der Dateinamen und MD5-Hashes zum Hashen selbst übergeben. Dadurch wird ein einzelner Wert zurückgegeben, der sich nur ändert, wenn sich der Inhalt einer der Dateien im Baum ändert.
Funktioniert leider find -s
nur mit BSD find(1), das in macOS, FreeBSD, NetBSD und OpenBSD verwendet wird. Um etwas Vergleichbares auf einem System mit GNU oder SUS find(1) zu erhalten, benötigen Sie etwas Hässlicheres:
$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum
Wir haben das Verhalten von BSD nachgeahmt, find -s
indem wir einen Aufruf von hinzugefügt haben sort
. Das -k 2
Bit weist es an, den MD5-Hash zu überspringen, sodass es nur die Dateinamen sortiert, die in Feld 2 bis zum Zeilenende nach sort
der Berechnung von stehen.
Diese Version des Befehls hat eine Schwäche: Sie kann leicht zu Verwirrung führen, wenn Sie Dateinamen mit Zeilenumbrüchen haben, da der Aufruf dann wie mehrere Zeilen aussieht sort
. Die find -s
Variante hat dieses Problem nicht, da die Baumdurchquerung und Sortierung im selben Programm erfolgen find
.
In beiden Fällen ist die Sortierung notwendig, um Fehlalarme zu vermeiden: Die gängigsten Unix/Linux-Dateisysteme halten die Verzeichnislisten nicht in einer stabilen, vorhersehbaren Reihenfolge. Das fällt Ihnen vielleicht nicht auf, wenn Sie ls
und ähnliche Programme verwenden, die den Verzeichnisinhalt stillschweigend für Sie sortieren. Wenn Sie den Befehl aufrufen, find
ohne die Ausgabe in irgendeiner Weise zu sortieren, entspricht die Reihenfolge der Zeilen in der Ausgabe der Reihenfolge, in der sie vom zugrunde liegenden Dateisystem zurückgegeben werden. Dies führt dazu, dass dieser Befehl einen geänderten Hashwert ausgibt, wenn sich die Reihenfolge der ihm als Eingabe übergebenen Dateien ändert, selbst wenn die Daten identisch bleiben.
Sie fragen sich vielleicht, ob das -k 2
Bit im obigen GNU- sort
Befehl notwendig ist. Da der Hash der Dateidaten ein angemessener Proxy für den Dateinamen ist, solange sich der Inhalt nicht geändert hat, erhalten wir keine falschen Ergebnisse, wenn wir diese Option weglassen, sodass wir denselben Befehl sowohl mit GNU als auch mit BSD verwenden können sort
. Bedenken Sie jedoch, dass es eine kleine Chance gibt (1:2 128 mit MD5), dass die genaue Reihenfolge der Dateinamen nicht mit der Teilreihenfolge übereinstimmt, die sich durch den Verzicht -k 2
ergeben kann, wenn es jemals zu einer Hash-Kollision kommt. Bedenken Sie jedoch, dass dieser ganze Ansatz für Sie wahrscheinlich nicht in Frage kommt, wenn für Ihre Anwendung so kleine Chancen einer Nichtübereinstimmung von Bedeutung sind.
md5sum
Möglicherweise müssen Sie die Befehle in oder eine andere Hash-Funktion ändern md5
. Wenn Sie eine andere Hash-Funktion wählen und die zweite Form des Befehls für Ihr System benötigen, müssen Sie den sort
Befehl möglicherweise entsprechend anpassen. Eine weitere Falle ist, dass einige Datensummierungsprogramme überhaupt keinen Dateinamen ausgeben. Ein Paradebeispiel ist das alte Unix- sum
Programm.
Diese Methode ist etwas ineffizient, da sie md5sum
N+1-mal aufgerufen wird, wobei N die Anzahl der Dateien im Baum ist. Dies ist jedoch ein notwendiger Aufwand, um das Hashen von Datei- und Verzeichnismetadaten zu vermeiden.
Option 2: Daten vergleichenUndMetadaten
Wenn Sie das erkennen müssenirgendetwasin einem Baum hat sich geändert, nicht nur Dateiinhalte. Bitten Sie tar
darum, den Verzeichnisinhalt für Sie zu packen und senden Sie ihn dann an md5sum
:
$ tar -cf - somedir | md5sum
Da tar
auch Dateiberechtigungen, Eigentümer usw. angezeigt werden, werden auch Änderungen an diesen Dingen erkannt, nicht nur Änderungen am Dateiinhalt.
Diese Methode ist erheblich schneller, da sie nur einen Durchgang über den Baum macht und das Hash-Programm nur einmal ausführt.
Wie bei der find
oben beschriebenen based-Methode tar
werden die Dateinamen in der Reihenfolge verarbeitet, in der das zugrunde liegende Dateisystem sie zurückgibt. Es kann durchaus sein, dass Sie in Ihrer Anwendung sicher sein können, dass dies nicht passiert. Mir fallen mindestens drei verschiedene Nutzungsmuster ein, bei denen dies wahrscheinlich der Fall ist. (Ich werde sie nicht auflisten, da wir uns in das Gebiet nicht spezifizierten Verhaltens begeben. Jedes Dateisystem kann hier unterschiedlich sein, sogar von einer Betriebssystemversion zur nächsten.)
Wenn Sie falsche Ergebnisse erhalten, empfehle ich Ihnen, die find | cpio
Option inGilles' Antwort.
Antwort2
Die Prüfsumme muss eine deterministische und eindeutige Darstellung der Dateien als Zeichenfolge sein. Deterministisch bedeutet, dass Sie dasselbe Ergebnis erhalten, wenn Sie dieselben Dateien an denselben Speicherorten ablegen. Eindeutig bedeutet, dass zwei verschiedene Dateisätze unterschiedliche Darstellungen haben.
Daten und Metadaten
Ein guter Anfang ist, ein Archiv mit den Dateien zu erstellen. Dies ist eine eindeutige Darstellung (offensichtlich, da Sie die Dateien wiederherstellen können, indem Sie das Archiv extrahieren). Es kann Dateimetadaten wie Datum und Eigentümer enthalten. Dies ist jedoch noch nicht ganz richtig: Ein Archiv ist mehrdeutig, da seine Darstellung von der Reihenfolge abhängt, in der die Dateien gespeichert sind, und gegebenenfalls von der Komprimierung.
Eine Lösung besteht darin, die Dateinamen vor dem Archivieren zu sortieren. Wenn Ihre Dateinamen keine Zeilenumbrüche enthalten, können Sie find | sort
sie auflisten und in dieser Reihenfolge zum Archiv hinzufügen. Achten Sie darauf, dass Sie dem Archivierer sagen, dass er nicht in Verzeichnisse rekursiv vorgehen soll. Hier sind Beispiele mit POSIX pax
, GNU tar und 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
Nur Namen und Inhalte, die Low-Tech-Methode
Wenn Sie nur die Dateidaten und keine Metadaten berücksichtigen möchten, können Sie ein Archiv erstellen, das nur den Dateiinhalt enthält. Dafür gibt es jedoch keine Standardtools. Anstatt den Dateiinhalt einzuschließen, können Sie den Hash der Dateien einschließen. Wenn die Dateinamen keine Zeilenumbrüche enthalten und es nur normale Dateien und Verzeichnisse gibt (keine symbolischen Links oder speziellen Dateien), ist dies ziemlich einfach, Sie müssen jedoch einige Dinge beachten:
{ 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
Wir fügen zusätzlich zur Liste der Prüfsummen eine Verzeichnisliste hinzu, da leere Verzeichnisse sonst unsichtbar wären. Die Dateiliste ist sortiert (in einer bestimmten, reproduzierbaren Locale – danke an Peter.O für die Erinnerung daran). trennt echo
die beiden Teile (ohne dies könnten Sie einige leere Verzeichnisse erstellen, deren Namen wie md5sum
eine Ausgabe aussehen, die auch als normale Dateien durchgehen könnte). Wir fügen auch eine Liste der Dateigrößen hinzu, um zu vermeidenLängenverlängerungsangriffe.
MD5 ist übrigens veraltet. Wenn es verfügbar ist, sollten Sie SHA-2 oder zumindest SHA-1 verwenden.
Namen und Daten, Unterstützung von Zeilenumbrüchen in Namen
Hier ist eine Variante des obigen Codes, die auf GNU-Tools basiert, um die Dateinamen mit Nullbytes zu trennen. Dadurch können Dateinamen Zeilenumbrüche enthalten. Die GNU-Digest-Dienstprogramme setzen Sonderzeichen in ihrer Ausgabe in Anführungszeichen, sodass es keine mehrdeutigen Zeilenumbrüche gibt.
{ 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
Ein robusterer Ansatz
Hier ist ein minimal getestetes Python-Skript, das einen Hash erstellt, der eine Dateihierarchie beschreibt. Es berücksichtigt Verzeichnisse und Dateiinhalte und ignoriert symbolische Links und andere Dateien und gibt einen schwerwiegenden Fehler zurück, wenn eine Datei nicht gelesen werden kann.
#! /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()
Antwort3
Wenn Ihr Ziel lediglich darin besteht, Unterschiede zwischen zwei Verzeichnissen zu finden, sollten Sie die Verwendung von „diff“ in Erwägung ziehen.
Versuche dies:
diff -qr dir1 dir2
Antwort4
Verwendenchecksumdir
:
$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad
SchnellerUndEinfacherals die anderen Bash-Lösungen.