Linux: einen einzelnen Hash für einen bestimmten Ordner und Inhalt berechnen?

92

Sicherlich muss es einen Weg geben, dies einfach zu tun!

Ich habe die Linux-Befehlszeilen-Apps wie sha1sumund ausprobiert, md5sumaber sie scheinen nur in der Lage zu sein, Hashes einzelner Dateien zu berechnen und eine Liste von Hash-Werten auszugeben, einen für jede Datei.

Ich muss einen einzelnen Hash für den gesamten Inhalt eines Ordners generieren (nicht nur für die Dateinamen).

Ich würde gerne so etwas machen

sha1sum /folder/of/stuff > singlehashvalue

Bearbeiten: Zur Verdeutlichung befinden sich meine Dateien auf mehreren Ebenen in einem Verzeichnisbaum. Sie befinden sich nicht alle im selben Stammordner.

Ben L.
quelle
1
Mit "Gesamtinhalt" meinen Sie die logischen Daten aller Dateien im Verzeichnis oder deren Daten zusammen mit Meta, während Sie zum Root-Hash gelangen? Da die Auswahlkriterien für Ihren Anwendungsfall recht weit gefasst sind, habe ich versucht, in meiner Antwort einige praktische Kriterien anzusprechen.
Sechs-k

Antworten:

119

Ein möglicher Weg wäre:

sha1sum Pfad / zu / Ordner / * | sha1sum

Wenn es einen ganzen Verzeichnisbaum gibt, ist es wahrscheinlich besser, find und xargs zu verwenden. Ein möglicher Befehl wäre

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

Und schließlich, wenn Sie auch Berechtigungen und leere Verzeichnisse berücksichtigen müssen:

(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

Die Argumente dazu statbewirken, dass der Name der Datei gedruckt wird, gefolgt von den oktalen Berechtigungen. Die beiden Suchvorgänge werden nacheinander ausgeführt, wodurch die doppelte Menge an Festplatten-E / A verursacht wird. Der erste findet alle Dateinamen und prüft den Inhalt, der zweite findet alle Datei- und Verzeichnisnamen, druckt Namen und Modus. Die Liste der "Dateinamen und Prüfsummen", gefolgt von "Namen und Verzeichnissen mit Berechtigungen", wird dann für eine kleinere Prüfsumme mit einer Prüfsumme versehen.

Vatine
quelle
2
und vergessen Sie nicht, LC_ALL = POSIX zu setzen, damit die verschiedenen Tools eine vom Gebietsschema unabhängige Ausgabe erstellen.
David Schmitt
2
Ich habe cat | gefunden sha1sum soll erheblich schneller sein als sha1sum | sha1sum. YMMV, probieren Sie jedes dieser Elemente auf Ihrem System aus: time find path / to / folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; Zeit Pfad finden / zu / Ordner-Typ f -print0 | sort -z | xargs -0 cat | sha1sum
Bruno Bronosky
5
@RichardBronosky - Nehmen wir an, wir haben zwei Dateien, A und B. A enthält "foo" und B enthält "bar was here". Mit Ihrer Methode könnten wir das nicht von zwei Dateien C und D trennen, in denen C "foobar" und D "war hier" enthält. Indem wir jede Datei einzeln hashen und dann alle "Dateinamen-Hash" -Paare hashen, können wir den Unterschied erkennen.
Vatine
2
Damit dies unabhängig vom Verzeichnispfad funktioniert (dh wenn Sie die Hashes zweier verschiedener Ordner vergleichen möchten), müssen Sie einen relativen Pfad verwenden und in das entsprechende Verzeichnis wechseln, da die Pfade im endgültigen Hash enthalten sind:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles
2
@robbles Das ist richtig und warum ich keine Initiale /auf das path/to/folderBit gesetzt habe.
Vatine
25
  • Verwenden Sie ein Tool zur Erkennung von Dateisystemeinbrüchen wie aide .

  • Hash einen Teerball des Verzeichnisses:

    tar cvf - /path/to/folder | sha1sum

  • Codieren Sie selbst etwas wie den Oneliner von vatine :

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

David Schmitt
quelle
3
+1 für die Teerlösung. Das ist das schnellste, aber das Fallenlassen der v. Ausführlichkeit verlangsamt es nur.
Bruno Bronosky
6
Beachten Sie, dass bei der Teerauflösung davon ausgegangen wird, dass die Dateien beim Vergleich in derselben Reihenfolge vorliegen. Ob dies der Fall ist, hängt vom Dateisystem ab, in dem sich die Dateien beim Vergleich befinden.
Nr.
5
Der Git-Hash ist für diesen Zweck nicht geeignet, da der Dateiinhalt nur ein Teil seiner Eingabe ist. Selbst beim ersten Festschreiben eines Zweigs wird der Hash von der Festschreibungsnachricht und den Festschreibungsmetadaten beeinflusst, wie zum Beispiel dem Zeitpunkt des Festschreibens. Wenn Sie dieselbe Verzeichnisstruktur mehrmals festschreiben, erhalten Sie jedes Mal einen anderen Hash. Daher ist der resultierende Hash nicht geeignet, um festzustellen, ob zwei Verzeichnisse exakte Kopien voneinander sind, indem nur der Hash gesendet wird.
Zoltan
1
@Zoltan der Git-Hash ist vollkommen in Ordnung, wenn Sie einen Baum-Hash und keinen Commit-Hash verwenden.
Hobbs
@hobbs Die Antwort lautete ursprünglich "Commit Hash", was für diesen Zweck sicherlich nicht geeignet ist. Der Baum-Hash klingt nach einem viel besseren Kandidaten, aber es könnte immer noch versteckte Fallen geben. Mir fällt ein, dass das Setzen des ausführbaren Bits in einigen Dateien den Baum-Hash ändert. Sie müssen git config --local core.fileMode falsevor dem Festschreiben eine Ausgabe durchführen , um dies zu vermeiden. Ich weiß nicht, ob es noch solche Vorbehalte gibt.
Zoltan
14

Du kannst tun tar -c /path/to/folder | sha1sum

S.Lott
quelle
16
Wenn Sie diese Prüfsumme auf einem anderen Computer replizieren möchten, ist tar möglicherweise keine gute Wahl, da das Format offenbar mehrdeutig ist und in vielen Versionen vorhanden ist, sodass tar auf einem anderen Computer möglicherweise unterschiedliche Ausgaben aus denselben Dateien erzeugt.
Slowdog
2
Ungeachtet der berechtigten Bedenken von slowdog können Sie die --mtimeOption wie folgt hinzufügen, wenn Sie sich um Dateiinhalte, Berechtigungen usw., aber nicht um die Änderungszeit kümmern : tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
Binary Phile
@ S.Lott, wenn die Verzeichnisgröße groß ist, ich meine, wenn die Größe des Verzeichnisses so groß ist, wird es länger dauern, es zu komprimieren und md5 darauf zu bekommen
Kasun Siyambalapitiya
11

Wenn Sie nur überprüfen möchten, ob sich etwas im Ordner geändert hat, würde ich Folgendes empfehlen:

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

Sie erhalten lediglich einen Hash der ls-Ausgabe, die Ordner, Unterordner, ihre Dateien, ihren Zeitstempel, ihre Größe und ihre Berechtigungen enthält. So ziemlich alles, was Sie brauchen würden, um festzustellen, ob sich etwas geändert hat.

Bitte beachten Sie, dass dieser Befehl nicht für jede Datei einen Hash generiert. Deshalb sollte er schneller sein als die Verwendung von find.

Shumoapp
quelle
1
Ich bin mir nicht sicher, warum dies angesichts der Einfachheit der Lösung nicht mehr positive Stimmen hat. Kann jemand erklären, warum dies nicht gut funktionieren würde?
Dave C
1
Ich nehme an, dies ist nicht ideal, da der generierte Hash auf dem Dateieigentümer, der Einrichtung des Datumsformats usw. basiert.
Ryota
1
Der Befehl ls kann angepasst werden, um alles auszugeben, was Sie wollen. Sie können -l durch -gG ersetzen, um die Gruppe und den Eigentümer wegzulassen. Und Sie können das Datumsformat mit der Option --time-style ändern. Schauen Sie sich im Grunde die Manpage von ls an und finden Sie heraus, was Ihren Anforderungen entspricht.
Shumoapp
@ DaveC Weil es so ziemlich nutzlos ist. Wenn Sie Dateinamen vergleichen möchten, vergleichen Sie sie einfach direkt. Sie sind nicht so groß.
Navin
5
@Navin Aus der Frage geht nicht hervor, ob es notwendig ist, Dateiinhalte zu hashen oder eine Änderung in einem Baum zu erkennen. Jeder Fall hat seine Verwendung. Das Speichern von 45K-Dateinamen in einem Kernelbaum ist beispielsweise weniger praktisch als ein einzelner Hash. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum funktioniert gut für mich
yashma
5

Ein robuster und sauberer Ansatz

  • Das Wichtigste zuerst, belasten Sie nicht den verfügbaren Speicher ! Hash eine Datei in Blöcken, anstatt die gesamte Datei zu füttern.
  • Unterschiedliche Ansätze für unterschiedliche Bedürfnisse / Zwecke (alle unten aufgeführten oder wählen Sie aus, was auch immer gilt):
    • Hash nur den Eintragsnamen aller Einträge im Verzeichnisbaum
    • Hash den Dateiinhalt aller Einträge (lassen Sie das Meta wie, Inode-Nummer, ctime, atime, mtime, Größe usw., Sie bekommen die Idee)
    • Bei einem symbolischen Link ist sein Inhalt der Referenzname. Hash es oder überspringen
    • Folgen Sie dem Symlink oder folgen Sie ihm nicht (aufgelöster Name), während Sie den Inhalt des Eintrags hashen
    • Wenn es sich um ein Verzeichnis handelt, sind seine Inhalte nur Verzeichniseinträge. Während des rekursiven Durchlaufs werden sie schließlich gehasht. Sollten die Verzeichniseintragsnamen dieser Ebene gehasht werden, um dieses Verzeichnis zu kennzeichnen? Hilfreich in Anwendungsfällen, in denen der Hash erforderlich ist, um eine Änderung schnell zu identifizieren, ohne tief gehen zu müssen, um den Inhalt zu hashen. Ein Beispiel wäre die Änderung des Dateinamens, aber der Rest des Inhalts bleibt gleich und es handelt sich um ziemlich große Dateien
    • Behandeln Sie große Dateien gut (achten Sie auch hier auf den Arbeitsspeicher)
    • Behandeln Sie sehr tiefe Verzeichnisbäume (beachten Sie die geöffneten Dateideskriptoren).
    • Behandeln Sie nicht standardmäßige Dateinamen
    • Wie gehe ich mit Dateien vor, die Sockets, Pipes / FIFOs, Blockgeräte oder Char-Geräte sind? Müssen sie auch hashen?
    • Aktualisieren Sie die Zugriffszeit eines Eintrags während des Durchlaufs nicht, da dies ein Nebeneffekt ist und für bestimmte Anwendungsfälle kontraproduktiv (intuitiv?) Ist.

Dies ist, was ich auf meinem Kopf habe, jeder, der einige Zeit damit verbracht hat, praktisch daran zu arbeiten, hätte andere Fallstricke und Eckfälle gefangen.

Hier ist ein sehr speicherschonendes Tool , das die meisten Fälle anspricht, an den Rändern möglicherweise etwas rau ist, aber sehr hilfreich war.

Ein Beispiel für die Verwendung und Ausgabe von 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

Ein Ausschnitt aus menschenfreundlichen Ergebnissen:

...
... //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
Sechs-k
quelle
1
Können Sie ein kurzes Beispiel geben, um ein robustes und sauberes sha256 eines Ordners zu erhalten, möglicherweise für einen Windows-Ordner mit drei Unterverzeichnissen und jeweils einigen Dateien?
Ferit
3

Wenn Sie nur den Inhalt der Dateien hashen und die Dateinamen ignorieren möchten, können Sie verwenden

cat $FILES | md5sum

Stellen Sie sicher, dass Sie die Dateien in derselben Reihenfolge haben, wenn Sie den Hash berechnen:

cat $(echo $FILES | sort) | md5sum

Sie können jedoch keine Verzeichnisse in Ihrer Dateiliste haben.


quelle
2
Wenn Sie das Ende einer Datei an den Anfang der darauf folgenden Datei alphabetisch verschieben, wirkt sich dies nicht auf den Hash aus, sondern sollte dies tun. Ein Dateibegrenzer oder Dateilängen müssten im Hash enthalten sein.
Jason Stangroome
3

Wenn dies ein Git-Repo ist und Sie alle darin enthaltenen Dateien ignorieren .gitignoremöchten, möchten Sie möglicherweise Folgendes verwenden:

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

Das funktioniert gut für mich.

ndbroadbent
quelle
Vielen Dank! :)
Visortelle
Für viele Anwendungen ist dieser Ansatz überlegen. Wenn Sie nur die Quellcodedateien speichern, erhalten Sie in viel kürzerer Zeit einen ausreichend eindeutigen Hash.
John McGehee
2

Dafür gibt es ein Python-Skript:

http://code.activestate.com/recipes/576973-getting-the-sha-1-or-md5-hash-of-a-directory/

Wenn Sie die Namen einer Datei ändern, ohne ihre alphabetische Reihenfolge zu ändern, wird sie vom Hash-Skript nicht erkannt. Wenn Sie jedoch die Reihenfolge der Dateien oder den Inhalt einer Datei ändern, erhalten Sie beim Ausführen des Skripts einen anderen Hash als zuvor.

Kingdon
quelle
2

Ein weiteres Werkzeug, um dies zu erreichen:

http://md5deep.sourceforge.net/

Wie es sich anhört: wie md5sum, aber auch rekursiv, plus andere Funktionen.

Jack
quelle
1
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert.
Mamoun Benghezal
1

Versuchen Sie es in zwei Schritten:

  1. Erstellen Sie eine Datei mit Hashes für alle Dateien in einem Ordner
  2. Hash diese Datei

Wie so:

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

Oder alles auf einmal:

# cat `find /folder/of/stuff -type f | sort` | sha1sum
Joao da Silva
quelle
for F in 'find ...' ...funktioniert nicht, wenn Sie Leerzeichen in Namen haben (was Sie heutzutage immer tun).
Mivk
1

Ich würde die Ergebnisse für einzelne Dateien durch sort(um eine bloße Neuordnung von Dateien zu verhindern, um den Hash zu ändern) in md5sumoder sha1sumweiterleiten, je nachdem, was Sie wählen.

Rafał Dowgird
quelle
1

Ich habe dazu ein Groovy-Skript geschrieben:

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

Sie können die Verwendung anpassen, um zu vermeiden, dass jede Datei gedruckt wird, der Nachrichtenauszug geändert wird, Verzeichnis-Hashing ausgeführt wird usw. Ich habe sie anhand der NIST-Testdaten getestet und sie funktioniert wie erwartet. 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
nicht überprüft
quelle
1

Ich musste in ein ganzes Verzeichnis nach Dateiänderungen suchen.

Aber mit Ausschluss, Zeitstempeln, Verzeichnisbesitz.

Ziel ist es, überall eine identische Summe zu erhalten, wenn die Dateien identisch sind.

Einschließlich des Hostings auf anderen Computern, unabhängig von den Dateien, oder einer Änderung an diesen.

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

Es generiert eine Liste von Hashs nach Dateien und verkettet diese Hashes zu einem.

Dies ist viel schneller als die Teermethode.

Für eine stärkere Privatsphäre in unseren Hashes können wir sha512sum nach demselben Rezept verwenden.

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

Die Hashes sind mit sha512sum auch überall identisch , es ist jedoch keine Möglichkeit bekannt, sie umzukehren.

NVRM
quelle
Dies scheint viel einfacher zu sein als die akzeptierte Antwort für das Hashing eines Verzeichnisses. Ich fand die akzeptierte Antwort nicht zuverlässig. Ein Problem ... gibt es eine Chance, dass die Hashes in einer anderen Reihenfolge herauskommen? sha256sum /tmp/thd-agent/* | sortist das, was ich für eine zuverlässige Bestellung versuche, dann nur das Hashing.
Thinktt
Hallo, es sieht so aus, als ob die Hashes standardmäßig in alphabetischer Reihenfolge vorliegen. Was verstehen Sie unter zuverlässiger Bestellung? Sie müssen das alles selbst organisieren. Verwenden Sie beispielsweise assoziative Arrays, Eintrag + Hash. Wenn Sie dieses Array nach Eintrag sortieren, erhalten Sie eine Liste der berechneten Hashes in der Sortierreihenfolge. Ich glaube, Sie können ein json-Objekt anderweitig verwenden und das gesamte Objekt direkt hashen.
NVRM
Wenn ich verstehe, sagen Sie, dass die Dateien in alphabetischer Reihenfolge gehasht werden. Das scheint richtig zu sein. Etwas in der oben akzeptierten Antwort gab mir manchmal zeitweise andere Befehle, also versuche ich nur sicherzustellen, dass das nicht noch einmal passiert. Ich werde am Ende beim Sortieren bleiben. Scheint zu funktionieren. Das einzige Problem mit dieser Methode im Vergleich zur akzeptierten Antwort ist, dass verschachtelte Ordner nicht behandelt werden. In meinem Fall habe ich keine Ordner, daher funktioniert dies hervorragend.
Thinktt
was ist mit ls -r | sha256sum?
NVRM
@NVRM hat es versucht und es wurde nur nach Änderungen des Dateinamens
gesucht
0

Sie können sha1sumdie Liste der Hash-Werte generieren und sha1sumdiese Liste dann erneut. Dies hängt davon ab, was genau Sie erreichen möchten.

Ronny Vindenes
quelle
0

Hier ist eine einfache, kurze Variante in Python 3, die für kleine Dateien (z. B. einen Quellbaum oder etwas, bei dem jede Datei einzeln problemlos in den Arbeitsspeicher passt) problemlos funktioniert. Dabei werden leere Verzeichnisse ignoriert, basierend auf den Ideen der anderen Lösungen:

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

Es funktioniert so:

  1. Suchen Sie alle Dateien im Verzeichnis rekursiv und sortieren Sie sie nach Namen
  2. Berechnen Sie den Hash (Standard: SHA-1) jeder Datei (liest die gesamte Datei in den Speicher)
  3. Erstellen Sie einen Textindex mit den Zeilen "Dateiname = Hash"
  4. Codieren Sie diesen Index zurück in eine UTF-8-Byte-Zeichenfolge und hashen Sie diese

Sie können eine andere Hash-Funktion als zweiten Parameter übergeben, wenn SHA-1 nicht Ihre Tasse Tee ist.

Thomas Perl
quelle