Le moyen le plus rapide de savoir si deux fichiers ont le même contenu sous Unix / Linux?

232

J'ai un script shell dans lequel je dois vérifier si deux fichiers contiennent les mêmes données ou non. Je le fais pour beaucoup de fichiers, et dans mon script, la diffcommande semble être le goulot d'étranglement des performances.

Voici la ligne:

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

if ($status) then ...

Pourrait-il y avoir un moyen plus rapide de comparer les fichiers, peut-être un algorithme personnalisé au lieu de celui par défaut diff?

JDS
la source
10
C'est vraiment un problème, mais vous ne demandez pas si deux fichiers sont identiques, vous demandez si deux fichiers ont un contenu identique. Les mêmes fichiers ont des inodes identiques (et le même périphérique).
Zano
1
Contrairement à la réponse acceptée, la mesure de cette réponse ne reconnaît aucune différence notable entre diffet cmp.
wedi

Réponses:

390

Je crois que cmpcela s'arrêtera à la première différence d'octet:

cmp --silent $old $new || echo "files are different"
Alex Howansky
la source
1
Comment puis-je ajouter plus de commandes qu'une seule? Je veux copier un fichier et lancer un vol.
feedc0de
9
cmp -s $old $newfonctionne également. -sest l'abréviation de--silent
Rohmer
7
Pour augmenter la vitesse, vous devez vérifier que les tailles de fichier sont égales avant de comparer le contenu. Est-ce que quelqu'un sait si cmp fait ça?
BeowulfNode42
3
Pour exécuter plusieurs commandes, vous pouvez utiliser des crochets: cmp -s old new || {écho non; faire écho à; écho même; }
unfa
6
@ BeowulfNode42 oui, toute implémentation décente cmpvérifiera d'abord la taille du fichier. Voici la version GNU, si vous voulez voir les optimisations supplémentaires qu'elle inclut: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham
54

J'aime @Alex Howansky qui a utilisé 'cmp --silent' pour cela. Mais j'ai besoin d'une réponse à la fois positive et négative, donc j'utilise:

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

Je peux ensuite l'exécuter dans le terminal ou avec un ssh pour comparer les fichiers avec un fichier constant.

mec pn1
la source
16
Si votre echo successcommande (ou toute autre commande que vous mettez à sa place) échoue, votre commande "réponse négative" sera exécutée. Vous devez utiliser une construction "if-then-else-fi". Par exemple, comme cet exemple simple .
Wildcard
18

Pourquoi n'obtenez-vous pas le hachage du contenu des deux fichiers?

Essayez ce script, appelez-le par exemple script.sh puis exécutez-le comme suit: 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
jabaldonedo
la source
2
@THISUSERNEEDSHELP C'est parce que les algorithmes de hachage ne sont pas un à un. Ils sont conçus de manière à ce que l'espace de hachage soit grand et que différentes entrées aient de grandes chances de produire différents hachages. La réalité est cependant que l'espace de hachage est fini, tandis que la plage de fichiers possibles à hacher ne l'est pas - vous finirez par avoir une collision. En cryptologie, cela s'appelle l' attaque d'anniversaire .
le
5
@will Eh, c'est effectivement garanti de fonctionner. Les chances que cela ne fonctionne pas sont, mathématiquement parlant, autour 1/(2^511). À moins que vous ne craigniez que quelqu'un essaye intentionnellement de créer une collision, l'idée que cette méthode produise un faux positif n'est pas vraiment une préoccupation sérieuse. cmpest encore plus efficace, car il n'a pas à lire l'intégralité du fichier dans le cas où les fichiers ne correspondent pas.
Ajedi32
12
OP a demandé la méthode la plus RAPIDE ... la recherche du premier bit non correspondant (en utilisant cmp) ne serait-elle pas plus rapide (si elles ne correspondent pas) que le hachage de tout le fichier, surtout si les fichiers sont volumineux?
KoZm0kNoT
3
md5 est préférable si vous faites une comparaison un à plusieurs. Vous pouvez stocker le hachage md5 en tant qu'attribut ou dans une base de données pour chaque fichier. Si un nouveau fichier apparaît et que vous devez vérifier si le même fichier existe n'importe où sur le système de fichiers, il vous suffit de calculer le hachage du nouveau fichier et de vérifier par rapport à tous les précédents. Je suis sûr que Git utilise le hachage pour vérifier les modifications de fichiers lors d'une validation, mais ils utilisent SHA1.
JimHough
3
@ BeowulfNode42 C'est pourquoi j'ai préfacé mon commentaire avec "Sauf si vous êtes inquiet à propos de quelqu'un essayant intentionnellement de créer une collision"
Ajedi32
5

Parce que je suis nul et que je n'ai pas assez de points de réputation, je ne peux pas ajouter cette friandise comme commentaire.

Mais, si vous allez utiliser la cmpcommande (et n'avez pas besoin / ne voulez pas être verbeux), vous pouvez simplement saisir l'état de sortie. Par la cmppage de manuel:

Si un FICHIER est «-» ou manquant, lisez l'entrée standard. L'état de sortie est 0 si les entrées sont les mêmes, 1 si différent, 2 en cas de problème.

Donc, vous pourriez faire quelque chose comme:

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
Gregory Martin
la source
oui, mais c'est en fait une façon de faire plus compliquée cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fiqui, à son tour, est une façon de faire plus compliquée cmp --silent $FILE1 $FILE2 || echo "files differ"car vous pouvez utiliser directement la commande dans l'expression. Il remplace $?. En conséquence, l'état existant de la commande sera comparé. Et c'est ce que fait l'autre réponse. btw. Si quelqu'un a du mal --silent, ce n'est pas pris en charge partout (busybox). use-s
papo
4

Pour les fichiers qui ne sont pas différents, toute méthode nécessitera d'avoir lu les deux fichiers entièrement, même si la lecture a été dans le passé.

Il n'y a pas d'alternative. La création de hachages ou de sommes de contrôle à un moment donné nécessite donc la lecture de l'intégralité du fichier. Les gros fichiers prennent du temps.

La récupération des métadonnées de fichiers est beaucoup plus rapide que la lecture d'un fichier volumineux.

Alors, existe-t-il des métadonnées de fichier que vous pouvez utiliser pour établir que les fichiers sont différents? Taille du fichier ? ou même les résultats de la commande file qui ne fait que lire une petite partie du fichier?

Exemple de fragment de code de taille de fichier:

  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'  

Si les fichiers sont de la même taille, vous êtes bloqué avec des lectures complètes de fichiers.

Jim Mcnamara
la source
1
Utilisez ls -npour éviter les problèmes si les noms d'utilisateur ou de groupe ont des espaces.
tricasse
2

Essayez également d'utiliser la commande cksum:

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

La commande cksum affichera le nombre d'octets d'un fichier. Voir «man cksum».

Nono Taps
la source
2
C'était aussi ma première pensée. Cependant, les hachages ont du sens si vous devez comparer plusieurs fois le même fichier, car le hachage n'est calculé qu'une seule fois. Si vous ne le comparez qu'une seule fois, puis md5lit tout le fichier de toute façon, donc cmp, s'arrêter à la première différence, sera beaucoup plus rapide.
Francesco Dondi
0

En faisant des tests avec un Raspberry Pi 3B + (j'utilise un système de fichiers en superposition et j'ai besoin de synchroniser périodiquement), j'ai effectué une comparaison de moi-même pour diff -q et cmp -s; notez qu'il s'agit d'un journal à l'intérieur de / dev / shm, donc les vitesses d'accès au disque ne sont pas un problème:

[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

Je l'ai couru plusieurs fois. cmp -s avait systématiquement des temps légèrement plus courts sur la boîte de test que j'utilisais. Donc, si vous voulez utiliser cmp -s pour faire des choses entre deux fichiers ....

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"
Jack Simth
la source