Il modo più veloce per dire se due file hanno gli stessi contenuti in Unix / Linux?


232

Ho uno script di shell in cui devo verificare se due file contengono gli stessi dati o meno. Faccio questo per molti file e nel mio script il diffcomando sembra essere il collo di bottiglia delle prestazioni.

Ecco la linea:

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

if ($status) then ...

Potrebbe esserci un modo più veloce per confrontare i file, forse un algoritmo personalizzato anziché quello predefinito diff?


10
Questo è davvero nitpicking, ma non stai chiedendo di vedere se due file sono uguali, stai chiedendo se due file hanno lo stesso contenuto. Gli stessi file hanno inode identici (e stesso dispositivo).
Zano,

1
A differenza della risposta accettata, la misurazione in questa risposta non riconosce alcuna differenza notevole tra diffe cmp.
wedi,

Risposte:


390

Credo che cmpsi fermerà alla prima differenza di byte:

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

1
Come posso aggiungere più comandi di uno solo? Voglio copiare un file e fare il boot.
feedc0de,

9
cmp -s $old $newfunziona anche. -sè l'abbreviazione di--silent
Rohmer

7
Per aumentare la velocità, è necessario verificare che le dimensioni del file siano uguali prima di confrontare il contenuto. Qualcuno sa se cmp fa questo?
BeowulfNode42,

3
Per eseguire più comandi, è possibile utilizzare parentesi: cmp -s old new || {echo no; echo il; echo stesso; }
unfa

6
@ BeowulfNode42 sì, qualsiasi implementazione decente cmpverificherà prima le dimensioni del file. Ecco la versione GNU, se vuoi vedere le ulteriori ottimizzazioni che include: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham

54

Mi piace che @Alex Howansky abbia usato 'cmp --silent' per questo. Ma ho bisogno di una risposta sia positiva che negativa, quindi uso:

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

Posso quindi eseguirlo nel terminale o con un SSH per controllare i file con un file costante.


16
Se il tuo echo successcomando (o qualunque altro comando tu abbia posto al suo posto) fallisce, il tuo comando "risposta negativa" verrà eseguito. Dovresti usare un costrutto "if-then-else-fi". Ad esempio, come questo semplice esempio .
Wildcard il

18

Perché non ottieni l'hash del contenuto di entrambi i file?

Prova questo script, chiamalo ad esempio script.sh e quindi eseguilo come segue: 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 È perché gli algoritmi di hashing non sono uno a uno. Sono progettati in modo tale che lo spazio di hashing sia ampio e che input diversi abbiano un'alta probabilità di produrre hash diversi. La realtà è che lo spazio hash è limitato, mentre la gamma di file possibili per l'hash non lo è - alla fine si avrà una collisione. In crittografia si chiama Attacco di compleanno .
sarà il

5
@will Eh, è effettivamente garantito il funzionamento. Le probabilità che non funzioni sono matematicamente parlando 1/(2^511). A meno che tu non sia preoccupato per qualcuno che cerca intenzionalmente di creare una collisione l'idea di questo metodo che produce un falso positivo non è davvero una preoccupazione seria. cmpè comunque più efficiente, poiché non deve leggere l'intero file nel caso in cui i file non corrispondano.
Ajedi32,

12
OP ha chiesto il modo PIÙ VELOCE ... la ricerca del primo bit non corrispondente (usando cmp) non sarebbe più veloce (se non corrispondono) rispetto all'hashing dell'intero file, specialmente se i file sono grandi?
KoZm0kNoT

3
md5 è il migliore se stai facendo un confronto da uno a molti. È possibile memorizzare l'hash md5 come attributo o in un database su ogni file. Se viene visualizzato un nuovo file e devi verificare se lo stesso file esiste in qualsiasi punto del file system, tutto ciò che fai è calcolare l'hash del nuovo file e verificare con tutti i precedenti. Sono sicuro che Git usa l'hash per verificare le modifiche ai file durante un commit ma usano SHA1.
JimHough

3
@ BeowulfNode42 Ecco perché ho preceduto il mio commento con "A meno che tu non sia preoccupato per qualcuno che cerca intenzionalmente di creare una collisione"
Ajedi32

5

Perché faccio schifo e non ho abbastanza punti reputazione, non posso aggiungere questo bocconcino come commento.

Ma se stai per usare il cmpcomando (e non hai bisogno / vuoi essere prolisso) puoi semplicemente prendere lo stato di uscita. Per la cmppagina man:

Se un FILE è '-' o mancante, leggere l'input standard. Lo stato di uscita è 0 se gli ingressi sono uguali, 1 se diverso, 2 in caso di problemi.

Quindi, potresti fare qualcosa del tipo:

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

sì, ma questo è in realtà un modo più complicato di fare cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fiche a sua volta è un modo più complicato di fare cmp --silent $FILE1 $FILE2 || echo "files differ"perché puoi usare direttamente il comando nell'espressione. Sostituisce $?. Di conseguenza lo stato esistente del comando verrà confrontato. Ed è quello che fa l'altra risposta. btw. Se qualcuno è alle prese con --silent, non è supportato ovunque (busybox). uso-s
papo,

4

Per i file che non sono diversi, qualsiasi metodo richiederà di aver letto entrambi i file, anche se la lettura era in passato.

Non c'è alternativa. Quindi la creazione di hash o checksum a un certo punto nel tempo richiede la lettura dell'intero file. I file di grandi dimensioni richiedono tempo.

Il recupero dei metadati dei file è molto più veloce della lettura di un file di grandi dimensioni.

Quindi, ci sono dei metadati di file che puoi usare per stabilire che i file sono diversi? Dimensione del file ? o anche i risultati del comando file che legge solo una piccola parte del file?

Frammento di codice di esempio dimensione file:

  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'  

Se i file hanno le stesse dimensioni, allora sei bloccato con letture di file complete.


1
Utilizzare ls -nper evitare problemi se i nomi utente o gruppo hanno spazi bianchi.
tricasse

2

Prova anche a usare il comando 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

Il comando cksum genererà il conteggio dei byte di un file. Vedi "man cksum".


2
È stato anche il mio primo pensiero. Tuttavia, gli hash hanno senso se devi confrontare lo stesso file più volte, poiché l'hash viene calcolato solo una volta. Se lo stai confrontando una sola volta, md5legge comunque l'intero file, quindi cmp, fermandoti alla prima differenza, sarà molto più veloce.
Francesco Dondi,

0

Facendo alcuni test con un Raspberry Pi 3B + (sto usando un file system overlay e devo sincronizzarlo periodicamente), ho eseguito un mio confronto per diff -q e cmp -s; si noti che questo è un registro da dentro / dev / shm, quindi le velocità di accesso al disco non sono un problema:

[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

L'ho eseguito un paio di volte. cmp -s ha avuto tempi leggermente più brevi sulla scatola di test che stavo usando. Quindi se vuoi usare cmp -s per fare cose tra due file ....

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.