Localizando arquivos duplicados e substituindo-os por links simbólicos

12

Estou tentando encontrar uma maneira de verificar dentro de um determinado diretório arquivos duplicados (mesmo com nomes diferentes) e substituí-los por links simbólicos apontando para a primeira ocorrência. Eu tentei com fdupes , mas apenas lista essas duplicatas.
Esse é o contexto: estou personalizando um tema de ícones do meu gosto, e descobri que muitos ícones, mesmo que tenham nomes e locais diferentes dentro de sua pasta pai, e são usados para finalidades diferentes, basicamente são os mesmos. cenário. Como aplicar a mesma modificação vinte ou trinta vezes é redundante quando apenas uma é realmente necessária, quero manter apenas uma imagem e ligar simbolicamente todas as outras.

Como exemplo, se eu executar fdupes -r ./ dentro do diretório testdir , ele poderá retornar para mim os seguintes resultados:

./file1.png
./file2.png
./subdir1/anotherfile.png
./subdir1/subdir2/yetanotherfile.png

Dada essa saída, gostaria de manter apenas o arquivo file1.png , excluir todos os outros e substituí-los por links simbólicos apontando para ele, mantendo todos os nomes de arquivos originais. Portanto, file2.png reterá seu nome, mas se tornará um link para file1.png em vez de ser uma duplicata.

Esses links não devem apontar para um caminho absoluto, mas devem ser relativos ao diretório pai testdir ; ou seja, yetanotherfile.png será o ponto para ../../file1.png , não para /home/testuser/.icons/testdir/file1.png

Estou interessado em soluções que envolvam uma GUI e CLI. Não é obrigatório o uso de fdupes . Eu o citei porque é uma ferramenta que conheço, mas estou aberto a soluções que também usam outras ferramentas.

Tenho certeza de que um script bash para lidar com tudo isso não deve ser tão difícil de criar, mas eu não sou especialista o suficiente para descobrir como escrevê-lo sozinho.

    
por Sekhemty 14.09.2014 / 16:33

5 respostas

3

Primeiro; Existe uma razão pela qual você precisa usar links simbólicos e não os links habituais? Eu estou tendo dificuldade em entender a necessidade de links simbólicos com caminhos relativos. Aqui está como eu resolveria esse problema:

Eu acho que a versão Debian (Ubuntu) dos fdupes pode substituir duplicatas com hard links usando a opção -L , mas eu não tenho uma instalação Debian para verificar isso.

Se você não tem uma versão com a opção -L , você pode usar este pequeno script bash que eu encontrei em commandlinefu .
Note que esta sintaxe só funcionará no bash.

fdupes -r -1 path | while read line; do master=""; for file in ${line[*]}; do if [ "x${master}" == "x" ]; then master=$file; else ln -f "${master}" "${file}"; fi; done; done

O comando acima irá encontrar todos os arquivos duplicados em "path" e substituí-los por hardlinks. Você pode verificar isso executando ls -ilR e observando o inode número. Aqui está um samle com dez arquivos idênticos:

$ ls -ilR

total 20
3094308 -rw------- 1 username group  5 Sep 14 17:21 file
3094311 -rw------- 1 username group  5 Sep 14 17:21 file2
3094312 -rw------- 1 username group  5 Sep 14 17:21 file3
3094313 -rw------- 1 username group  5 Sep 14 17:21 file4
3094314 -rw------- 1 username group  5 Sep 14 17:21 file5
3094315 drwx------ 1 username group 48 Sep 14 17:22 subdirectory

./subdirectory:
total 20
3094316 -rw------- 1 username group 5 Sep 14 17:22 file
3094332 -rw------- 1 username group 5 Sep 14 17:22 file2
3094345 -rw------- 1 username group 5 Sep 14 17:22 file3
3094346 -rw------- 1 username group 5 Sep 14 17:22 file4
3094347 -rw------- 1 username group 5 Sep 14 17:22 file5

Todos os arquivos têm números de inode separados, tornando-os arquivos separados. Agora vamos desduplicá-los:

$ fdupes -r -1 . | while read line; do j="0"; for file in ${line[*]}; do if [ "$j" == "0" ]; then j="1"; else ln -f ${line// .*/} $file; fi; done; done
$ ls -ilR
.:
total 20
3094308 -rw------- 10 username group  5 Sep 14 17:21 file
3094308 -rw------- 10 username group  5 Sep 14 17:21 file2
3094308 -rw------- 10 username group  5 Sep 14 17:21 file3
3094308 -rw------- 10 username group  5 Sep 14 17:21 file4
3094308 -rw------- 10 username group  5 Sep 14 17:21 file5
3094315 drwx------  1 username group 48 Sep 14 17:24 subdirectory

./subdirectory:
total 20
3094308 -rw------- 10 username group 5 Sep 14 17:21 file
3094308 -rw------- 10 username group 5 Sep 14 17:21 file2
3094308 -rw------- 10 username group 5 Sep 14 17:21 file3
3094308 -rw------- 10 username group 5 Sep 14 17:21 file4
3094308 -rw------- 10 username group 5 Sep 14 17:21 file5

Os arquivos agora têm o mesmo número de inode, o que significa que todos apontam para o mesmo dados físicos no disco.

Espero que isso resolva seu problema ou, pelo menos, direcione você na direção certa!

    
por 14.09.2014 / 17:32
4

Eu tive uma situação parecida, mas no meu caso o link simbólico deveria apontar para um caminho relativo então eu escrevi este script python para fazer o truque:

#!/usr/bin/env python
# Reads fdupes(-r -1) output and create relative symbolic links for each duplicate
# usage: fdupes -r1 . | ./lndupes.py

import os
from os.path import dirname, relpath, basename, join
import sys

lines = sys.stdin.readlines()

for line in lines:
    files = line.strip().split(' ')
    first = files[0]
    print "First: %s "% first
    for dup in files[1:]:
        rel = os.path.relpath(dirname(first), dirname(dup))
        print "Linking duplicate: %s to %s" % (dup, join(rel,basename(first)))
        os.unlink(dup)
        os.symlink(join(rel,basename(first)), dup)

Para cada linha de entrada (que é uma lista de arquivos), o script divide a lista de arquivos (separados por espaços em branco), obtém o caminho relativo de cada arquivo para o primeiro e cria o link simbólico.

    
por 19.08.2016 / 21:12
2

Se você não gosta muito de scripts, recomendo rdfind . Que examinará os diretórios fornecidos em busca de arquivos duplicados e os vinculará de forma rígida ou flexível. Eu usei isso para desduplicar meu diretório de gemas de Ruby com grande sucesso. Está disponível no Debian / Ubuntu.

    
por 05.03.2018 / 22:51
1

Portanto, a resposta dada por arnefm (que foi copiada em toda a Internet) não lida com espaços em nomes de arquivos. Eu escrevi um script que lida com espaços em arquivos.

#!/bin/bash
fdupes -r -1 CHANGE_THIS_PATH | sed -e 's/\(\w\) /|/g' -e 's/|$//' > files
while read line; do
        IFS='|' read -a arr <<< "$line"
        orig=${arr[0]}
        for ((i = 1; i < ${#arr[@]}; i++)); do
                file="${arr[$i]}"
                ln -sf "$orig" "$file"
        done 
done < files

O que isto faz é encontrar dupes e escrevê-los PIPE separados em um arquivo chamado 'arquivos'.

Em seguida, ele lê o arquivo de volta, linha por linha, em uma matriz e cada elemento da matriz é delimitado pelo PIPE.

Em seguida, itera sobre todos os elementos não primeiros da matriz, substituindo o arquivo por um link simbólico para o primeiro elemento.

O arquivo externo ('arquivos') pode ser removido, se o comando fdupes for executado em um subshell, que é lido diretamente por enquanto, mas desta forma parece mais claro.

    
por 06.05.2015 / 08:55
0

Algumas advertências à frente:

  • BASH específico
  • Sem espaço nos nomes dos arquivos
  • Presume que cada linha contém no máximo 2 arquivos.

fdupes -1r common/base/dir | while read -r -a line ; do ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]}; done

Se mais de dois arquivos forem duplicados (por exemplo, arquivo1 arquivo2 arquivo3), precisaremos criar um link simbólico para cada par - tratar arquivo1, arquivo2 e arquivo1, arquivo3 como dois casos separados:

if [[ ${#line[@]} -gt 2 ]] ;then 
  ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]} 
  ln -sf $(realpath --relative-to ${line[2]} ${line[0]}) ${line[2]} 
  ...
fi

Gastar isso para lidar automaticamente com um número arbitrário de duplicatas por linha exigirá um pouco mais de esforço.

Outra abordagem seria primeiro criar links simbólicos para caminhos absolutos e, em seguida, convertê-los:

fdupes -1r /absolute/path/common/base/dir | while read -r -a line ; do ln -sf ${line[0]} ${line[1]}; done
chroot /absolute/path/common/base/dir ; symlinks -cr .

Isto é baseado na resposta de @Gilles: link

    
por 05.11.2017 / 13:32