O programa md5sum não fornece somas de verificação para diretórios. Quero obter uma única soma de verificação MD5 para todo o conteúdo de um diretório, incluindo arquivos em subdiretórios. Ou seja, uma soma de verificação combinada feita de todos os arquivos. Existe uma maneira de fazer isso?
Responder1
A maneira certa depende exatamente do motivo pelo qual você está perguntando:
Opção 1: comparar apenas dados
Se você só precisa de um hash do conteúdo do arquivo da árvore, isso resolverá o problema:
$ find -s somedir -type f -exec md5sum {} \; | md5sum
Isso primeiro resume todo o conteúdo do arquivo individualmente, em uma ordem previsível, depois passa a lista de nomes de arquivos e hashes MD5 para o próprio hash, fornecendo um único valor que só muda quando o conteúdo de um dos arquivos na árvore muda.
Infelizmente, find -s
só funciona com BSD find(1), usado em macOS, FreeBSD, NetBSD e OpenBSD. Para obter algo comparável em um sistema com GNU ou SUS find(1), você precisa de algo um pouco mais feio:
$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum
Imitamos o comportamento do BSD find -s
adicionando uma chamada para sort
. O -k 2
bit diz para pular o hash MD5, então ele classifica apenas os nomes dos arquivos, que estão no campo 2 até o final da linha pelo sort
cálculo de '.
Há um ponto fraco nesta versão do comando: ele pode ficar confuso se você tiver nomes de arquivos com novas linhas, porque a chamada parecerá com várias linhas sort
. A find -s
variante não tem esse problema, pois o percurso e a classificação da árvore acontecem dentro do mesmo programa find
.
Em ambos os casos, a classificação é necessária para evitar falsos positivos: os sistemas de arquivos Unix/Linux mais comuns não mantêm as listagens de diretórios em uma ordem estável e previsível. Você pode não perceber isso usando ls
e tal, que classifica silenciosamente o conteúdo do diretório para você. Chamar find
sem classificar sua saída de alguma forma fará com que a ordem das linhas na saída corresponda à ordem que o sistema de arquivos subjacente as retorna, o que fará com que este comando forneça um valor de hash alterado se a ordem dos arquivos fornecidos a ele como entrada mudar, mesmo que os dados permaneçam idênticos.
Você pode perguntar se o -k 2
bit no sort
comando GNU acima é necessário. Dado que o hash dos dados do arquivo é um proxy adequado para o nome do arquivo, desde que o conteúdo não tenha mudado, não obteremos falsos positivos se abandonarmos esta opção, permitindo-nos usar o mesmo comando com GNU e BSD sort
. No entanto, perceba que há uma pequena chance (1:2 128 com MD5) de que a ordem exata dos nomes dos arquivos não corresponda à ordem parcial que a ausência -k 2
pode fornecer se houver uma colisão de hash. Tenha em mente, entretanto, que se essas pequenas chances de incompatibilidade forem importantes para sua aplicação, toda essa abordagem provavelmente estará fora de questão para você.
Pode ser necessário alterar os md5sum
comandos para md5
ou alguma outra função hash. Se você escolher outra função hash e precisar da segunda forma do comando para o seu sistema, talvez seja necessário ajustar o sort
comando de acordo. Outra armadilha é que alguns programas de soma de dados não escrevem nenhum nome de arquivo, sendo um excelente exemplo o antigo sum
programa Unix.
Este método é um tanto ineficiente, chamando md5sum
N+1 vezes, onde N é o número de arquivos na árvore, mas esse é um custo necessário para evitar hash de metadados de arquivos e diretórios.
Opção 2: comparar dadoseMetadados
Se você precisa ser capaz de detectar issoqualquer coisaem uma árvore mudou, não apenas o conteúdo do arquivo, peça tar
para empacotar o conteúdo do diretório para você e envie-o para md5sum
:
$ tar -cf - somedir | md5sum
Como tar
também vê permissões de arquivo, propriedade, etc., isso também detectará alterações nessas coisas, não apenas alterações no conteúdo do arquivo.
Este método é consideravelmente mais rápido, pois faz apenas uma passagem pela árvore e executa o programa hash apenas uma vez.
Tal como acontece com o find
método baseado acima, tar
processará os nomes dos arquivos na ordem em que o sistema de arquivos subjacente os retorna. Pode ser que em seu aplicativo você tenha certeza de que isso não acontecerá. Posso pensar em pelo menos três padrões de uso diferentes onde esse provavelmente será o caso. (Não vou listá-los, porque estamos entrando em um território de comportamento não especificado. Cada sistema de arquivos pode ser diferente aqui, mesmo de uma versão do sistema operacional para outra.)
Se você receber falsos positivos, recomendo escolher a find | cpio
opção emResposta de Gilles.
Responder2
A soma de verificação precisa ser uma representação determinística e inequívoca dos arquivos como uma string. Determinístico significa que se você colocar os mesmos arquivos nos mesmos locais, obterá o mesmo resultado. Inequívoco significa que dois conjuntos diferentes de arquivos têm representações diferentes.
Dados e metadados
Criar um arquivo contendo os arquivos é um bom começo. Esta é uma representação inequívoca (obviamente, já que você pode recuperar os arquivos extraindo o arquivo). Pode incluir metadados de arquivo, como datas e propriedade. No entanto, isto ainda não está certo: um arquivo é ambíguo, porque a sua representação depende da ordem em que os ficheiros são armazenados e, se aplicável, da compressão.
Uma solução é classificar os nomes dos arquivos antes de arquivá-los. Se os nomes dos seus arquivos não contiverem novas linhas, você poderá find | sort
listá-los e adicioná-los ao arquivo nesta ordem. Tome cuidado para informar ao arquivador para não recorrer aos diretórios. Aqui estão exemplos com POSIX pax
, GNU tar e cpio:
find | LC_ALL=C sort | pax -w -d | md5sum
find | LC_ALL=C sort | tar -cf - -T - --no-recursion | md5sum
find | LC_ALL=C sort | cpio -o | md5sum
Somente nomes e conteúdos, do jeito low-tech
Se você deseja levar em consideração apenas os dados do arquivo e não os metadados, você pode fazer um arquivo que inclua apenas o conteúdo do arquivo, mas não existem ferramentas padrão para isso. Em vez de incluir o conteúdo do arquivo, você pode incluir o hash dos arquivos. Se os nomes dos arquivos não contiverem novas linhas e houver apenas arquivos e diretórios regulares (sem links simbólicos ou arquivos especiais), isso será bastante fácil, mas você precisará cuidar de algumas coisas:
{ export LC_ALL=C;
find -type f -exec wc -c {} \; | sort; echo;
find -type f -exec md5sum {} + | sort; echo;
find . -type d | sort; find . -type d | sort | md5sum;
} | md5sum
Incluímos uma listagem de diretórios além da lista de somas de verificação, caso contrário os diretórios vazios seriam invisíveis. A lista de arquivos é classificada (em um local específico e reproduzível — obrigado a Peter.O por me lembrar disso). echo
separa as duas partes (sem isso, você poderia criar alguns diretórios vazios cujo nome parecesse md5sum
uma saída que também poderia passar por arquivos comuns). Também incluímos uma lista de tamanhos de arquivo, para evitarataques de extensão de comprimento.
A propósito, o MD5 está obsoleto. Se estiver disponível, considere usar SHA-2 ou pelo menos SHA-1.
Nomes e dados, suportando novas linhas em nomes
Aqui está uma variante do código acima que depende de ferramentas GNU para separar os nomes dos arquivos com bytes nulos. Isso permite que os nomes dos arquivos contenham novas linhas. Os utilitários GNU digest citam caracteres especiais em sua saída, para que não haja novas linhas ambíguas.
{ export LC_ALL=C;
du -0ab | sort -z; # file lengths, including directories (with length 0)
echo | tr '\n' '\000'; # separator
find -type f -exec sha256sum {} + | sort -z; # file hashes
echo | tr '\n' '\000'; # separator
echo "End of hashed data."; # End of input marker
} | sha256sum
Uma abordagem mais robusta
Aqui está um script Python minimamente testado que cria um hash descrevendo uma hierarquia de arquivos. Ele leva diretórios e conteúdo de arquivos para contas e ignora links simbólicos e outros arquivos, e retorna um erro fatal se algum arquivo não puder ser lido.
#! /usr/bin/env python
import hashlib, hmac, os, stat, sys
## Return the hash of the contents of the specified file, as a hex string
def file_hash(name):
f = open(name)
h = hashlib.sha256()
while True:
buf = f.read(16384)
if len(buf) == 0: break
h.update(buf)
f.close()
return h.hexdigest()
## Traverse the specified path and update the hash with a description of its
## name and contents
def traverse(h, path):
rs = os.lstat(path)
quoted_name = repr(path)
if stat.S_ISDIR(rs.st_mode):
h.update('dir ' + quoted_name + '\n')
for entry in sorted(os.listdir(path)):
traverse(h, os.path.join(path, entry))
elif stat.S_ISREG(rs.st_mode):
h.update('reg ' + quoted_name + ' ')
h.update(str(rs.st_size) + ' ')
h.update(file_hash(path) + '\n')
else: pass # silently symlinks and other special files
h = hashlib.sha256()
for root in sys.argv[1:]: traverse(h, root)
h.update('end\n')
print h.hexdigest()
Responder3
Se o seu objetivo é apenas encontrar diferenças entre dois diretórios, considere usar o diff.
Experimente isto:
diff -qr dir1 dir2
Responder4
Usarchecksumdir
:
$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad
Mais rápidoemais fácildo que as outras soluções bash.