Ich habe eine Menge Musik in einem Verzeichnisbaum gespeichert, der Qualität halber in dem Format, in dem ich sie ursprünglich gespeichert habe. Ich habe einen zweiten Verzeichnisbaum, der eine ähnliche Struktur hat, aber alle Dateien in einem verlustbehaftet komprimierten Format enthält, das von meinem Telefon abgespielt werden kann, und mit gelegentlichen Metadatenänderungen (z. B. Entfernen eingebetteter Cover, um Platz zu sparen).
Mir fällt auf, dass es bei einem erheblichen Teil der Musik keinen Unterschied zwischen den beiden Fällen gibt – im Allgemeinen, wenn die verteilte Version nur als MP3/OGG verfügbar war und keine eingebetteten Cover hatte. Festplattenspeicher mag billig sein, aber das ist kein Grund, ihn zu verschwenden. Gibt es eine Möglichkeit, Folgendes zu skripten:
- Suchen Sie in zwei Verzeichnissen nach identischen Dateien
- Wenn identische Dateien gefunden werden, ersetzen Sie eine durch einen Hardlink zur anderen
- Ohne sich beispielsweise die Zeit zu nehmen, ein vollständiges Diff zu erhalten, aus Zeitgründen
- Aber immer noch ohne das Risiko, versehentlich eine Kopie zweier nicht identischer Dateien zu löschen, was zwar eine geringe, aber nicht Null-Wahrscheinlichkeit ist, wenn ich beispielsweise nur Hashes vergleichen würde?
Antwort1
Mit dem folgenden Befehl md5
wird ein MD5-Digest für alle Dateien im aktuellen Verzeichnis oder darunter erstellt:
find . -type f -exec md5 {} +
Ersetzen Sie md5
es durch md5sum --tag
, wenn Sie nicht über das BSD- md5
Dienstprogramm verfügen.
Lassen Sie uns ein einfaches Skript erstellen, um dies in den Verzeichnissen durchzuführen:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
Dies nimmt zwei Verzeichnisse in die Befehlszeile auf und erzeugt Dateien mit den Namen md5.1
und md5.2
, eine Datei für jedes Verzeichnis in /tmp
(oder wohin $TMPDIR
auch immer). Diese Dateien werden nach dem MD5-Digest sortiert.
Die Dateien sehen folgendermaßen aus
MD5 (<path>) = <MD5 digest>
mit einer solchen Zeile für jede Datei.
Vergleichen Sie dann im selben Skript die Prüfsumme der beiden Dateien:
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12]
Dies führt eine relationale „Join“-Operation zwischen den beiden Dateien durch, wobei die Prüfsumme als Join-Feld verwendet wird. Alle Zeilen, die in den beiden Feldern die gleiche Prüfsumme aufweisen, werden zusammengeführt und ausgegeben.
Wenn eine Prüfsumme in beiden Dateien gleich ist, wird Folgendes ausgegeben:
<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)
Dies kann direkt weitergegeben werden, awk
um die beiden Pfade zu analysieren:
awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'
Dies ist nur eine Möglichkeit zu sagen, dass wir jede Zeile in Felder basierend auf und -F [()]
unterteilen möchten . Dadurch erhalten wir die Pfade in den Feldern 2 und 4.(
)
Dies würde ausgeben
<path1><tab><path2>
Dann müssen Sie nur noch diese durch Tabulatoren getrennten Pfadpaare lesen und die richtigen Befehle eingeben, um die Links zu erstellen:
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
In Summe:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12] |
awk -F '\\)|\\(' 'BEGIN { OFS="\t" } { print $2, $4 }' |
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
rm -f "$tmpdir"/md5.[12]
Die echo
Schleife while
dient der Sicherheit. Führen Sie sie einmal aus, um zu sehen, was passiert, und entfernen Sie sie und führen Sie sie erneut aus, wenn Sie sicher sind, dass sie das Richtige tut.
Denken Sie daran, dass Hardlinks nicht partitionsübergreifend sein können. Das bedeutet, dass beide Verzeichnisse auf derselben Partition liegen müssen. Die Dateien imzweiteVerzeichnis wird überschrieben, wenn festgestellt wird, dass es sich um Duplikate handelt. Bewahren Sie irgendwo eine Sicherungskopie der Originale auf, bis Sie mit dem Ergebnis zufrieden sind!
Beachten Sie, dass diese Lösung nicht ordnungsgemäß funktioniert, wenn der Dateiname „ (
oder“ )
oder „Tabulator“ enthält.
Antwort2
Sofern Sie nicht über eine große Sammlung sehr ähnlicher Dateien verfügen, beschleunigt das Berechnen und Vergleichen von Hashes das Auffinden von Duplikaten nicht. Der langsamste Vorgang ist das Lesen von der Festplatte. Das Berechnen eines Hashes bedeutet, die gesamte Datei zu lesen, und bei modernen kryptografisch starken Hashes ist dies außerdem eine CPU-intensive Aufgabe.
Wir müssen Daten nur vergleichen, wenn die Dateilängen unterschiedlich sind. Wenn es nur eine Datei mit einer bestimmten Länge gibt, gibt es offensichtlich keine Duplikate. Wenn es zwei gibt, ist ein einfacher Vergleich immer effizienter als Hashing. Wenn es drei oder mehr gibt, steigt die Anzahl der Vergleiche, aber es besteht die Möglichkeit, dass sie sich in den ersten Bytes oder Blöcken unterscheiden, sodass der Festplatten-E/A immer noch niedrig ist und wiederholte Lesevorgänge aus dem Cache zurückgegeben werden.
Deshalb empfehle ich, eine rekursive Verzeichnisliste zu erstellen, die eine Liste mit Länge und Pfadnamen enthält, die Liste dann numerisch zu sortieren und sich schließlich nur mit Dateisätzen gleicher Länge zu befassen, indem man sie paarweise vergleicht. Wenn zwei Dateien übereinstimmen, kann eine durch einen Hardlink ersetzt werden.