A maneira mais rápida de saber se dois arquivos têm o mesmo conteúdo no Unix / Linux?

231

Eu tenho um script de shell no qual preciso verificar se dois arquivos contêm os mesmos dados ou não. Faço isso para muitos arquivos e, no meu script, o diffcomando parece ser o gargalo de desempenho.

Aqui está a linha:

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

if ($status) then ...

Poderia haver uma maneira mais rápida de comparar os arquivos, talvez um algoritmo personalizado em vez do padrão diff?

JDS
fonte
10
Isso é realmente interessante, mas você não está pedindo para ver se dois arquivos são iguais, mas se dois arquivos têm conteúdo idêntico. Os mesmos arquivos têm inodes idênticos (e o mesmo dispositivo).
Zano
1
Diferentemente da resposta aceita, a medida nesta resposta não reconhece nenhuma diferença notável entre diffe cmp.
Quarta-

Respostas:

390

Acredito que cmpirá parar na primeira diferença de bytes:

cmp --silent $old $new || echo "files are different"
Alex Howansky
fonte
1
Como posso adicionar mais comandos que apenas um? Eu quero copiar um arquivo e roboot.
feedc0de
9
cmp -s $old $newtambém funciona. -sé curto para--silent
Rohmer 5/16
7
Como um aumento de velocidade, você deve verificar se os tamanhos dos arquivos são iguais antes de comparar o conteúdo. Alguém sabe se o cmp faz isso?
BeowulfNode42
3
Para executar vários comandos, você pode usar colchetes: cmp -s old new || {não eco; eco o; eco mesmo; }
unfa 15/03
6
@ BeowulfNode42 sim, qualquer implementação decente cmpirá verificar primeiro o tamanho do arquivo. Aqui está a versão GNU, se você quiser ver as otimizações adicionais que inclui: git.savannah.gnu.org/cgit/diffutils.git/tree/src/cmp.c
Ryan Graham
54

Eu gosto de @Alex Howansky ter usado 'cmp --silent' para isso. Mas eu preciso de respostas positivas e negativas, então eu uso:

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

Posso então executar isso no terminal ou com um ssh para verificar os arquivos em um arquivo constante.

pn1 cara
fonte
16
Se o seu echo successcomando (ou qualquer outro comando que você colocar no lugar) falhar, o comando "resposta negativa" será executado. Você deve usar uma construção "if-then-else-fi". Por exemplo, como este exemplo simples .
Curinga
18

Por que você não obtém o hash do conteúdo dos dois arquivos?

Experimente esse script, chame-o, por exemplo, script.sh e execute-o da seguinte maneira: 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
fonte
2
@THISUSERNEEDSHELP É porque os algoritmos de hash não são um para um. Eles são projetados de forma que o espaço de hash seja grande e diferentes entradas tenham uma grande chance de produzir hashes diferentes. A realidade é que o espaço do hash é finito, enquanto o intervalo de arquivos possíveis para o hash não é - eventualmente você terá uma colisão. Em criptologia, é chamado de ataque de aniversário .
será
5
@ Will Eh, é efetivamente garantido que funcione. As chances de isso não funcionar são, matematicamente falando, por aí 1/(2^511). A menos que você esteja preocupado com alguém tentando intencionalmente criar uma colisão, a idéia desse método produzindo um falso positivo não é realmente uma preocupação séria. cmpainda é mais eficiente, pois não precisa ler o arquivo inteiro no caso em que os arquivos não coincidem.
precisa saber é o seguinte
12
O OP pediu a maneira MAIS RÁPIDA ... não seria mais rápido procurar pelo primeiro bit não correspondente (usando cmp) (se não corresponderem) do que fazer o hash de todo o arquivo, especialmente se os arquivos forem grandes?
KoZm0kNoT
3
MD5 é melhor se você estiver fazendo uma comparação de um para muitos. Você pode armazenar o hash md5 como um atributo ou em um banco de dados em cada arquivo. Se um novo arquivo aparecer e você precisar verificar se o mesmo arquivo existe em qualquer lugar do sistema de arquivos, tudo o que você faz é calcular o hash do novo arquivo e comparar com todos os anteriores. Tenho certeza que o Git usa hash para verificar alterações de arquivo durante uma confirmação, mas eles usam SHA1.
precisa
3
@ BeowulfNode42 É por isso que prefaciou meu comentário com "A menos que você está preocupado com alguém intencionalmente tentando criar uma colisão"
Ajedi32
5

Como sou péssima e não tenho pontos de reputação suficientes, não posso adicionar esse detalhe como comentário.

Mas, se você for usar o cmpcomando (e não precisar / quiser ser detalhado), basta pegar o status de saída. Pela cmppágina do manual:

Se um ARQUIVO estiver '-' ou ausente, leia a entrada padrão. O status de saída é 0 se as entradas forem iguais, 1 se diferente, 2 se houver problema.

Então, você poderia fazer 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
fonte
sim, mas essa é realmente uma maneira mais complicada de fazer, cmp --silent $FILE1 $FILE2 ; if [ "$?" == "1" ]; then echo "files differ"; fique por sua vez é uma maneira mais complicada de fazer, cmp --silent $FILE1 $FILE2 || echo "files differ"porque você pode usar o comando na expressão diretamente. Substitui por $?. Como resultado, o status existente do comando será comparado. E é isso que a outra resposta faz. btw. Se alguém está enfrentando problemas --silent, ele não é suportado em qualquer lugar (busybox). use-s
papo
4

Para arquivos que não são diferentes, qualquer método exigirá a leitura completa dos dois arquivos, mesmo que a leitura tenha sido no passado.

Não ha alternativa. Portanto, criar hashes ou somas de verificação em algum momento requer a leitura de todo o arquivo. Arquivos grandes levam tempo.

A recuperação de metadados do arquivo é muito mais rápida que a leitura de um arquivo grande.

Portanto, existem metadados de arquivo que você pode usar para estabelecer que os arquivos são diferentes? Tamanho do arquivo ? ou até mesmo resultados do comando file, que lê apenas uma pequena parte do arquivo?

Fragmento de código de exemplo de tamanho de arquivo:

  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 os arquivos forem do mesmo tamanho, você ficará com as leituras completas do arquivo.

jim mcnamara
fonte
1
Use ls -npara evitar problemas se os nomes de usuários ou grupos tiverem espaços em branco.
tricasse
2

Tente também usar o 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

O comando cksum produzirá a contagem de bytes de um arquivo. Veja 'man cksum'.

Nono Taps
fonte
2
Esse foi o meu primeiro pensamento também. No entanto, os hashes fazem sentido se você precisar comparar o mesmo arquivo várias vezes, pois o hash é calculado apenas uma vez. Se você estiver comparando apenas uma vez, então md5lê o arquivo inteiro de qualquer maneira, então cmp, parar na primeira diferença será muito mais rápido.
Francesco Dondi
0

Fazendo alguns testes com um Raspberry Pi 3B + (estou usando um sistema de arquivos de sobreposição e preciso sincronizar periodicamente), fiz uma comparação própria para diff -q e cmp -s; observe que este é um log de dentro de / dev / shm, portanto a velocidade de acesso ao disco não é um 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

Eu o executei algumas vezes. O cmp -s consistentemente teve tempos um pouco mais curtos na caixa de teste que eu estava usando. Então, se você quiser usar cmp -s para fazer coisas entre dois arquivos ...

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
fonte