Der schnellste Weg, um festzustellen, ob zwei Dateien unter Unix / Linux denselben Inhalt haben?


231

Ich habe ein Shell-Skript, in dem ich überprüfen muss, ob zwei Dateien dieselben Daten enthalten oder nicht. Ich mache dies für viele Dateien, und in meinem Skript diffscheint der Befehl der Leistungsengpass zu sein.

Hier ist die Zeile:

diff -q $dst $new > /dev/null

if ($status) then ...

Könnte es eine schnellere Möglichkeit geben, die Dateien zu vergleichen, möglicherweise einen benutzerdefinierten Algorithmus anstelle des Standardalgorithmus diff?


10
Dies ist wirklich ein Trottel, aber Sie fragen nicht, ob zwei Dateien gleich sind, sondern ob zwei Dateien identischen Inhalt haben. Dieselben Dateien haben identische Inodes (und dasselbe Gerät).
Zano

1
Im Gegensatz zur akzeptierten Antwort erkennt die Messung in dieser Antwort keinen nennenswerten Unterschied zwischen diffund cmp.
Mi

Antworten:


388

Ich glaube, cmpwird beim ersten Byte Unterschied aufhören:

cmp --silent $old $new || echo "files are different"

1
Wie kann ich mehr als nur einen Befehl hinzufügen? Ich möchte eine Datei kopieren und roboot.
feedc0de

9
cmp -s $old $newfunktioniert auch. -sist die Abkürzung für--silent
Rohmer

7
Um die Geschwindigkeit zu erhöhen, sollten Sie überprüfen, ob die Dateigrößen gleich sind, bevor Sie den Inhalt vergleichen. Weiß jemand, ob cmp dies tut?
BeowulfNode42

3
Um mehrere Befehle auszuführen, können Sie Klammern verwenden: cmp -s old new || {Echo nicht; Echo der; Echo gleich; }
unfa

6
@ BeowulfNode42 Ja, bei jeder anständigen Implementierung cmpwird zuerst die Dateigröße überprüft. Hier ist die GNU-Version, wenn Sie die zusätzlichen Optimierungen sehen möchten, die sie enthält: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham

53

Ich mag @Alex Howansky hat dafür 'cmp --silent' verwendet. Aber ich brauche sowohl positive als auch negative Antworten, also benutze ich:

cmp --silent file1 file2 && echo '### SUCCESS: Files Are Identical! ###' || echo '### WARNING: Files Are Different! ###'

Ich kann dies dann im Terminal oder mit einem SSH ausführen, um Dateien gegen eine konstante Datei zu prüfen.


16
Wenn Ihr echo successBefehl (oder ein anderer Befehl, den Sie an seine Stelle gesetzt haben) fehlschlägt, wird Ihr Befehl "Negative Antwort" ausgeführt. Sie sollten ein "if-then-else-fi" -Konstrukt verwenden. Zum Beispiel wie dieses einfache Beispiel .
Wildcard

18

Warum erhalten Sie nicht den Hash beider Dateiinhalte?

Probieren Sie dieses Skript aus, rufen Sie es beispielsweise script.sh auf und führen Sie es dann wie folgt aus: script.sh file1.txt file2.txt

#!/bin/bash

file1=`md5 $1`
file2=`md5 $2`

if [ "$file1" = "$file2" ]
then
    echo "Files have the same content"
else
    echo "Files have NOT the same content"
fi

2
@THISUSERNEEDSHELP Das liegt daran, dass Hashing-Algorithmen nicht eins zu eins sind. Sie sind so konzipiert, dass der Hashing-Bereich groß ist und unterschiedliche Eingaben eine hohe Wahrscheinlichkeit haben, unterschiedliche Hashes zu erzeugen. Die Realität ist jedoch, dass der Hash-Bereich endlich ist, während der Bereich der möglichen Dateien für den Hash nicht begrenzt ist - schließlich kommt es zu einer Kollision. In der Kryptologie heißt es Geburtstagsangriff .
wird

5
@will Eh, es ist effektiv garantiert zu funktionieren. Die Wahrscheinlichkeit, dass es nicht funktioniert, ist mathematisch gesehen gegeben 1/(2^511). Wenn Sie sich keine Sorgen über jemanden machen, der absichtlich versucht, eine Kollision zu erzeugen, ist die Idee, dass diese Methode ein falsches Positiv erzeugt, kein ernstes Problem. cmpist jedoch immer noch effizienter, da nicht die gesamte Datei gelesen werden muss, wenn die Dateien nicht übereinstimmen.
Ajedi32

12
OP hat nach dem SCHNELLSTEN Weg gefragt ... wäre die Suche nach dem ersten nicht übereinstimmenden Bit (mit cmp) nicht schneller (wenn sie nicht übereinstimmen) als das Hashing der gesamten Datei, insbesondere wenn die Dateien groß sind?
KoZm0kNoT

3
md5 ist am besten, wenn Sie einen Eins-zu-Viele-Vergleich durchführen. Sie können den md5-Hash als Attribut oder in einer Datenbank für jede Datei speichern. Wenn eine neue Datei angezeigt wird und Sie überprüfen müssen, ob dieselbe Datei irgendwo im Dateisystem vorhanden ist, müssen Sie nur den Hash der neuen Datei berechnen und mit allen vorherigen vergleichen. Ich bin mir sicher, dass Git Hashing verwendet, um während eines Commits nach Dateiänderungen zu suchen, aber sie verwenden SHA1.
JimHough

3
@ BeowulfNode42 Aus diesem Grund habe ich meinem Kommentar "Es sei denn, Sie machen sich Sorgen um jemanden, der absichtlich versucht, eine Kollision zu verursachen"
vorangestellt

5

Da ich lutsche und nicht genug Reputationspunkte habe, kann ich diesen Leckerbissen nicht als Kommentar hinzufügen.

Wenn Sie jedoch den cmpBefehl verwenden möchten (und nicht ausführlich sein müssen / möchten), können Sie einfach den Exit-Status abrufen. Per cmpManpage:

Wenn eine DATEI '-' ist oder fehlt, lesen Sie die Standardeingabe. Der Ausgangsstatus ist 0, wenn die Eingänge gleich sind, 1, wenn sie unterschiedlich sind, 2, wenn Probleme auftreten.

Sie könnten also Folgendes tun:

STATUS="$(cmp --silent $FILE1 $FILE2; echo $?)"  # "$?" gives exit status for each comparison

if [[$STATUS -ne 0]]; then  # if status isn't equal to 0, then execute code
    DO A COMMAND ON $FILE1
else
    DO SOMETHING ELSE
fi

Ja, aber dies ist tatsächlich eine kompliziertere Vorgehensweise, cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fidie wiederum komplizierter ist, cmp --silent $FILE1 $FILE2 || echo "files differ"da Sie den Befehl direkt im Ausdruck verwenden können. Es ersetzt $?. Infolgedessen wird der vorhandene Status des Befehls verglichen. Und genau das macht die andere Antwort. Übrigens. Wenn jemand --silentProbleme hat, wird es nicht überall unterstützt (Busybox). use-s
papo

4

Für Dateien, die nicht unterschiedlich sind, muss für jede Methode beide Dateien vollständig gelesen werden, auch wenn der Lesevorgang in der Vergangenheit stattgefunden hat.

Es gibt keine Alternative. Um zu einem bestimmten Zeitpunkt Hashes oder Prüfsummen zu erstellen, muss die gesamte Datei gelesen werden. Große Dateien brauchen Zeit.

Das Abrufen von Dateimetadaten ist viel schneller als das Lesen einer großen Datei.

Gibt es also Dateimetadaten, mit denen Sie feststellen können, dass die Dateien unterschiedlich sind? Dateigröße ? oder sogar Ergebnisse des Dateibefehls, der nur einen kleinen Teil der Datei liest?

Beispielcodefragment für Dateigröße:

  ls -l $1 $2 | 
  awk 'NR==1{a=$5} NR==2{b=$5} 
       END{val=(a==b)?0 :1; exit( val) }'

[ $? -eq 0 ] && echo 'same' || echo 'different'  

Wenn die Dateien dieselbe Größe haben, bleiben Sie beim vollständigen Lesen der Dateien hängen.


1
Verwenden Sie ls -ndiese Option , um Probleme zu vermeiden, wenn Benutzer- oder Gruppennamen Leerzeichen enthalten.
Trikasse

2

Versuchen Sie auch, den Befehl cksum zu verwenden:

chk1=`cksum <file1> | awk -F" " '{print $1}'`
chk2=`cksum <file2> | awk -F" " '{print $1}'`

if [ $chk1 -eq $chk2 ]
then
  echo "File is identical"
else
  echo "File is not identical"
fi

Der Befehl cksum gibt die Byteanzahl einer Datei aus. Siehe 'man cksum'.


2
Das war auch mein erster Gedanke. Hashes sind jedoch sinnvoll, wenn Sie dieselbe Datei mehrmals vergleichen müssen, da der Hash nur einmal berechnet wird. Wenn Sie es nur einmal vergleichen, md5liest es trotzdem die gesamte Datei. Wenn Sie also cmpbeim ersten Unterschied anhalten, ist dies viel schneller.
Francesco Dondi

0

Bei einigen Tests mit einem Raspberry Pi 3B + (ich verwende ein Overlay-Dateisystem und muss regelmäßig synchronisieren) habe ich einen eigenen Vergleich für diff -q und cmp -s durchgeführt. Beachten Sie, dass dies ein Protokoll aus / dev / shm ist, sodass die Geschwindigkeit des Festplattenzugriffs kein Problem darstellt:

[root@mypi shm]# dd if=/dev/urandom of=test.file bs=1M count=100 ; time diff -q test.file test.copy && echo diff true || echo diff false ; time cmp -s test.file test.copy && echo cmp true || echo cmp false ; cp -a test.file test.copy ; time diff -q test.file test.copy && echo diff true || echo diff false; time cmp -s test.file test.copy && echo cmp true || echo cmp false
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 6.2564 s, 16.8 MB/s
Files test.file and test.copy differ

real    0m0.008s
user    0m0.008s
sys     0m0.000s
diff false

real    0m0.009s
user    0m0.007s
sys     0m0.001s
cmp false
cp: overwrite âtest.copyâ? y

real    0m0.966s
user    0m0.447s
sys     0m0.518s
diff true

real    0m0.785s
user    0m0.211s
sys     0m0.573s
cmp true
[root@mypi shm]# pico /root/rwbscripts/utils/squish.sh

Ich habe es ein paar Mal ausgeführt. cmp -s hatten auf der von mir verwendeten Testbox durchweg etwas kürzere Zeiten. Wenn Sie also cmp -s verwenden möchten, um Dinge zwischen zwei Dateien zu erledigen ...

identical (){
  echo "$1" and "$2" are the same.
  echo This is a function, you can put whatever you want in here.
}
different () {
  echo "$1" and "$2" are different.
  echo This is a function, you can put whatever you want in here, too.
}
cmp -s "$FILEA" "$FILEB" && identical "$FILEA" "$FILEB" || different "$FILEA" "$FILEB"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.