Tenho muitas músicas em uma árvore em um diretório, salvas em qualquer formato em que as coloquei inicialmente, para garantir a qualidade. Eu tenho uma segunda árvore de diretórios que é semelhante em estrutura, mas com todos os arquivos em um formato compactado com perdas reproduzíveis pelo meu telefone e com alterações ocasionais de metadados (por exemplo, remoção de capas incorporadas para economizar espaço).
Ocorre-me que, para uma parte significativa da música, não há diferença entre os dois casos - geralmente quando a versão distribuída estava disponível apenas como mp3/ogg e não tinha capas incorporadas. O espaço no disco rígido pode ser barato, mas não é motivo para desperdiçá-lo. Existe uma maneira de criar um script:
- Verifique se há arquivos idênticos em dois diretórios
- Sempre que forem encontrados arquivos idênticos, substitua um por um hardlink para o outro
- Sem, por exemplo, perder tempo para obter uma comparação completa, no interesse do tempo
- Mas ainda sem o risco de excluir acidentalmente uma cópia de dois arquivos não idênticos, o que é uma chance remota, mas diferente de zero, se eu, por exemplo, apenas comparar hashes?
Responder1
O seguinte é usado md5
para produzir um resumo MD5 para todos os arquivos no diretório atual ou abaixo:
find . -type f -exec md5 {} +
Substitua md5
por md5sum --tag
se você não tiver o md5
utilitário BSD.
Vamos construir um script simples para fazer isso nos diretórios:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
Isso leva dois diretórios na linha de comando e produz arquivos chamados md5.1
e md5.2
, um arquivo para cada diretório, em /tmp
(ou para onde quer que $TMPDIR
esteja apontando). Esses arquivos são classificados no resumo MD5.
Os arquivos ficarão parecidos
MD5 (<path>) = <MD5 digest>
com uma dessas linhas para cada arquivo.
Em seguida, no mesmo script, compare a soma de verificação entre os dois arquivos:
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12]
Isso faz uma operação relacional de "junção" entre os dois arquivos, usando a soma de verificação como campo de junção. Quaisquer linhas que tenham a mesma soma de verificação nos dois campos serão mescladas e geradas.
Se alguma soma de verificação for igual em ambos os arquivos, a saída será:
<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)
Isso pode ser transmitido awk
diretamente para analisar os dois caminhos:
awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'
É -F [()]
apenas uma maneira de dizer que gostaríamos de dividir cada linha em campos baseados em (
e )
. Fazer isso nos deixa com os caminhos nos campos 2 e 4.
Isso produziria
<path1><tab><path2>
Depois é só ler esses pares de caminhos separados por tabulações e emitir os comandos corretos para criar os links:
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
Resumindo:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12] |
awk -F '\\)|\\(' 'BEGIN { OFS="\t" } { print $2, $4 }' |
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
rm -f "$tmpdir"/md5.[12]
O echo
in the while
loop existe para segurança. Execute-o uma vez para ver o que aconteceria e remova-o e execute-o novamente se tiver certeza de que está fazendo a coisa certa.
Lembre-se de que os links físicos não podem abranger partições. Isso significa que ambos os diretórios precisam residir na mesma partição. Os arquivos nosegundodiretório será sobrescrito se forem considerados duplicados. Mantenha um backup dos originais em algum lugar até ficar satisfeito com o resultado!
Observe que esta solução não funcionará corretamente se algum arquivo tiver (
ou )
uma guia em seus nomes de arquivo.
Responder2
A menos que você tenha uma grande coleção de arquivos muito semelhantes, calcular e comparar hashes não acelera o processo de localização de duplicatas. A operação mais lenta é uma leitura de disco. Calcular um hash significa ler o arquivo inteiro e também é uma tarefa que exige muito da CPU com hashes modernos criptograficamente fortes.
Temos que comparar os dados apenas se os comprimentos dos arquivos forem diferentes. Se houver apenas um arquivo com determinado comprimento, obviamente não há duplicatas. Se houver dois, simplesmente compará-los é sempre mais eficiente do que fazer hash. Se houver três ou mais, o número de comparações aumenta, mas é provável que elas difiram nos primeiros bytes ou blocos, de modo que a E/S do disco ainda seja baixa e leituras repetidas sejam retornadas do cache.
É por isso que eu recomendaria fazer uma listagem recursiva de diretórios preparando uma lista de comprimento + nome do caminho, depois classificar a lista numericamente e, finalmente, lidar apenas com conjuntos de arquivos que compartilham o mesmo comprimento, comparando-os aos pares. Se dois arquivos corresponderem, um poderá ser substituído por um hardlink.