Converta arquivos idênticos em hardlinks [duplicados]

1

Eu tenho muita música em uma árvore em um diretório, salvo em qualquer formato em que eu inicialmente a tenha, por qualidade. Eu tenho uma segunda árvore de diretórios que é semelhante em estrutura, mas com todos os arquivos em um formato compactado com perdas que podem ser reproduzidos pelo meu telefone e com alterações ocasionais de metadados (por exemplo, removendo capas embutidas para economizar espaço).

Ocorre-me que, para uma parte significativa da música, não há diferença entre as duas instâncias - 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 isso não é motivo para desperdiçá-lo. Existe uma maneira de script:

  1. Verifique arquivos idênticos em dois diretórios
  2. Sempre que arquivos idênticos forem encontrados, substitua um com um link físico pelo outro
  3. Sem, e. tomando o tempo para obter uma diferença completa, no interesse do tempo
  4. Mas ainda sem o risco de excluir acidentalmente uma cópia de dois arquivos não-idênticos, o que é uma chance remota, mas não nula, se eu fosse, por exemplo, apenas compare hashes?
por David Heyman 27.05.2017 / 20:40

2 respostas

2

O seguinte usa 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 utilitário BSD md5 .

Vamos criar 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 onde quer que $TMPDIR esteja apontando). Esses arquivos são classificados no resumo MD5.

Os arquivos serão parecidos com

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 de "junção" relacional entre os dois arquivos, usando a soma de verificação como o campo de junção. Todas as linhas que tiverem a mesma soma de verificação nos dois campos serão mescladas e produzidas.

Se qualquer soma de verificação for a mesma em ambos os arquivos, será exibida:

<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)

Isso pode ser passado para awk diretamente para analisar os dois caminhos:

awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'

O -F [()] é apenas uma maneira de dizer que gostaríamos de dividir cada linha em campos com base em ( e ) . Isso nos deixa com os caminhos nos campos 2 e 4.

Isso produziria

<path1><tab><path2>

É só uma questão de 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

Em resumo:

#!/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 no loop while está lá por 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 links físicos não podem abranger partições. Isso significa que os dois diretórios precisam estar na mesma partição. Os arquivos no diretório segundo serão sobrescritos se forem encontrados duplicados. Mantenha um backup dos originais em algum lugar até ficar satisfeito com o resultado!

Observe que essa solução não funcionará corretamente se algum arquivo tiver ( ou ) ou uma guia em seus nomes de arquivo.

    
por 27.05.2017 / 22:17
2

A menos que você tenha uma grande coleção de arquivos muito semelhantes, a computação e a comparação de hashes não aceleram o processo de encontrar duplicatas. A operação mais lenta é uma leitura de disco. Computar um hash significa ler todo o arquivo e também é uma tarefa intensiva da CPU com hashes criptograficamente strongs e modernos.

Nós temos que comparar os dados apenas se os tamanhos dos arquivos forem diferentes. Se houver apenas um arquivo com determinado tamanho, obviamente não há duplicatas. Se houver dois, simplesmente compará-los é sempre mais eficiente que hashing. Se houver três ou mais, o número de comparações aumentará, mas as chances são de que elas diferem nos primeiros bytes ou blocos, portanto, a E / S do disco ainda é baixa e as leituras repetidas são retornadas do cache.

É por isso que eu recomendaria fazer uma listagem de diretórios recursivos preparando uma lista de comprimento + nome de caminho, depois classificar numericamente a lista e, finalmente, lidar apenas com conjuntos de arquivos compartilhando o mesmo comprimento comparando-os em pares. Se dois arquivos corresponderem, um deles pode ser substituído por um link físico.

    
por 28.05.2017 / 08:42