¿La forma más rápida de saber si dos archivos tienen el mismo contenido en Unix / Linux?

232

Tengo un script de shell en el que necesito verificar si dos archivos contienen los mismos datos o no. Hago esto para muchos archivos, y en mi script el diffcomando parece ser el cuello de botella en el rendimiento.

Aquí está la línea:

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

if ($status) then ...

¿Podría haber una forma más rápida de comparar los archivos, tal vez un algoritmo personalizado en lugar del predeterminado diff?

JDS
fuente
10
Esto es realmente muy curioso, pero no está preguntando si dos archivos son iguales, está preguntando si dos archivos tienen contenido idéntico. Los mismos archivos tienen inodos idénticos (y el mismo dispositivo).
Zano
1
A diferencia de la respuesta aceptada, la medición en esta respuesta no reconoce ninguna diferencia notable entre diffy cmp.
Miércoles

Respuestas:

390

Creo que cmpse detendrá en la primera diferencia de bytes:

cmp --silent $old $new || echo "files are different"
Alex Howansky
fuente
1
¿Cómo puedo agregar más comandos que uno solo? Quiero copiar un archivo y roboot.
feedc0de
99
cmp -s $old $newTambién funciona. -ses la abreviatura de--silent
Rohmer
77
Para aumentar la velocidad, debe verificar que los tamaños de los archivos sean iguales antes de comparar el contenido. ¿Alguien sabe si cmp hace esto?
BeowulfNode42
3
Para ejecutar varios comandos, puede usar corchetes: cmp -s old new || {eco no; hacer eco de echo lo mismo; }
unfa
66
@ BeowulfNode42 sí, cualquier implementación decente cmpverificará primero el tamaño del archivo. Aquí está la versión de GNU, si desea ver las optimizaciones adicionales que incluye: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham el
54

Me gusta @Alex Howansky he usado 'cmp --silent' para esto. Pero necesito respuestas positivas y negativas, así que uso:

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

Luego puedo ejecutar esto en la terminal o con un ssh para verificar los archivos contra un archivo constante.

pn1 amigo
fuente
16
Si su echo successcomando (o cualquier otro comando que ponga en su lugar) falla, se ejecutará su comando de "respuesta negativa". Debe usar una construcción "if-then-else-fi". Por ejemplo, como este simple ejemplo .
Comodín el
18

¿Por qué no obtienes el hash del contenido de ambos archivos?

Pruebe este script, llámelo por ejemplo script.sh y luego ejecútelo de la siguiente manera: 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
fuente
2
@THISUSERNEEDSHELP Es porque los algoritmos de hash no son uno a uno. Están diseñados de tal manera que el espacio de hash es grande, y las diferentes entradas tienen una alta probabilidad de producir diferentes hashes. Sin embargo, la realidad es que el espacio de hash es finito, mientras que el rango de posibles archivos para hash no lo es; eventualmente tendrá una colisión. En criptología se llama el ataque de cumpleaños .
será
55
@will Eh, está efectivamente garantizado para trabajar. Las probabilidades de que no funcione son, matemáticamente hablando, alrededor 1/(2^511). A menos que le preocupe que alguien intente intencionalmente crear una colisión, la idea de que este método produzca un falso positivo no es realmente una preocupación seria. cmpSin embargo, es aún más eficiente, ya que no tiene que leer el archivo completo en el caso de que los archivos no coincidan.
Ajedi32
12
OP solicitó la forma MÁS RÁPIDA ... ¿no sería más rápido (si no coinciden) buscar el primer bit no coincidente (si no coinciden) que el hash de todo el archivo, especialmente si los archivos son grandes?
KoZm0kNoT
3
md5 es mejor si está haciendo una comparación de uno a muchos. Puede almacenar el hash md5 como un atributo o en una base de datos en cada archivo. Si aparece un nuevo archivo y tiene que verificar si el mismo archivo existe en alguna parte del sistema de archivos, entonces todo lo que debe hacer es calcular el hash del nuevo archivo y compararlo con todos los anteriores. Estoy seguro de que Git usa hashing para verificar los cambios de archivo durante una confirmación, pero usan SHA1.
JimHough
3
@ BeowulfNode42 Es por eso que presenté mi comentario con "A menos que esté preocupado porque alguien intente intencionalmente crear una colisión"
Ajedi32
5

Debido a que apesta y no tengo suficientes puntos de reputación, no puedo agregar este dato como comentario.

Pero, si va a usar el cmpcomando (y no necesita / quiere ser detallado), puede obtener el estado de salida. Por la cmppágina del manual:

Si falta un ARCHIVO '-', lea la entrada estándar. El estado de salida es 0 si las entradas son iguales, 1 si es diferente, 2 si hay problemas.

Entonces, podrías hacer algo como:

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
fuente
Sí, pero esta es en realidad una forma más complicada de hacerlo, cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fique a su vez es una forma más complicada de hacerlo cmp --silent $FILE1 $FILE2 || echo "files differ"porque puede usar el comando en la expresión directamente. Sustituye a $?. Como resultado, se comparará el estado del comando existente. Y eso es lo que hace la otra respuesta. por cierto. Si alguien está luchando --silent, no es compatible en todas partes (busybox). uso-s
papo
4

Para los archivos que no son diferentes, cualquier método requerirá haber leído ambos archivos por completo, incluso si la lectura fue en el pasado.

No hay alternativa. Por lo tanto, crear hashes o sumas de verificación en algún momento requiere leer todo el archivo. Los archivos grandes llevan tiempo.

La recuperación de metadatos de archivos es mucho más rápida que leer un archivo grande.

Entonces, ¿hay algún metadato de archivo que pueda usar para establecer que los archivos son diferentes? Tamaño del archivo ? o incluso resultados del comando de archivo que solo lee una pequeña porción del archivo?

Fragmento de código de ejemplo de tamaño de archivo:

  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 los archivos son del mismo tamaño, entonces está atascado con lecturas completas de archivos.

jim mcnamara
fuente
1
Utilícelo ls -npara evitar problemas si los nombres de usuarios o grupos tienen espacios en blanco.
tricasse
2

Intente también usar el 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

El comando cksum generará el recuento de bytes de un archivo. Ver 'man cksum'.

Nono Taps
fuente
2
Ese fue mi primer pensamiento también. Sin embargo, los hash tienen sentido si tiene que comparar el mismo archivo muchas veces, ya que el hash se calcula solo una vez. Si lo está comparando solo una vez, md5lee el archivo completo de todos modos, por lo tanto cmp, detenerse en la primera diferencia, será mucho más rápido.
Francesco Dondi
0

Haciendo algunas pruebas con un Raspberry Pi 3B + (estoy usando un sistema de archivos superpuestos y necesito sincronizar periódicamente), ejecuté una comparación propia para diff -q y cmp -s; tenga en cuenta que este es un registro desde adentro / dev / shm, por lo que las velocidades de acceso al disco no son 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

Lo corrí un par de veces. cmp -s siempre tuvo tiempos ligeramente más cortos en la caja de prueba que estaba usando. Entonces, si desea usar cmp -s para hacer cosas entre dos archivos ...

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
fuente