Localizando arquivos duplicados e substituí-los por links simbólicos

16

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, fdupesmas apenas lista essas duplicatas.
Esse é o contexto: estou personalizando um tema de ícone ao meu gosto, e descobri que muitos ícones, mesmo que tenham nomes diferentes e locais diferentes dentro de sua pasta pai, e sejam usados ​​para propósitos 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 vincular todas as outras.

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

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

Dada essa saída, eu 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, manterá seu nome, mas se tornará um link para, em file1.pngvez de ser uma duplicata.

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

Estou interessado tanto em soluções que envolvem uma GUI e CLI. Não é obrigatório usar o fdupesque citei, porque é uma ferramenta que eu conheço, mas estou aberto a soluções que usam outras ferramentas também.

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

Sekhemty
fonte

Respostas:

3

Primeiro; Existe uma razão para você precisar usar links simbólicos e não os links comuns? Estou com dificuldade para 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) do fdupes pode substituir duplicados por links físicos usando a -Lopção, mas não tenho uma instalação Debian para verificar isso.

Se você não possui uma versão com a -Lopção, pode usar este pequeno script do bash que encontrei no commandlinefu .
Observe que essa sintaxe funcionará apenas 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 encontrará todos os arquivos duplicados em "path" e os substituirá por hardlinks. Você pode verificar isso executando ls -ilRe observando o número do inode. Aqui está uma amostra 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 os mesmos dados físicos no disco.

Espero que isso resolva seu problema ou pelo menos aponte para a direção certa!

arnefm
fonte
Lembrei-me de que os fdupes têm a opção de substituir os dupes por links, @arnefm, mas não consigo ver nada no homem nem é uma opção no v1.51(Ubuntu 14.04.2 LTS).
Alastair
Meu fork jdupesem github.com/jbruchon/jdupes tem a -Lopção que faz a ligação física desejada de conjuntos duplicados.
Jody Lee Bruchon
Acabei de ajustar o script aqui. Ele ainda não manipula espaços, mas manipula outros caracteres especiais (eu tinha strings de consulta de URL nos arquivos). Além disso, a ${line//…/}peça não estava funcionando para mim, então fiz uma maneira mais limpa de obter o primeiro arquivo "mestre" para o hardlink.
IBBoard 31/03/19
1
Nós precisaríamos de softlinks relativos se estiver usando rsyncum tipo diferente de sistema de arquivos? Ou se o sistema de arquivos não preservar a hierarquia, por exemplo, é um servidor de backup que coloca tudo sob controle /«machine-name»/...? Ou se você deseja restaurar do backup? Não vejo como os hardlinks serão preservados aqui. Softlinks relativos teriam melhores chances de sobreviver, eu acho.
22419 Buddy
6

Se você não gosta de muitos scripts, posso recomendar o rdfind . O qual examinará os diretórios especificados em busca de arquivos duplicados e os vinculará de forma flexível ou virtual. Eu o usei para desduplicar meu diretório de gemas Ruby com grande sucesso. Está disponível no Debian / Ubuntu.

Andrew França
fonte
4

Eu tive uma situação semelhante, mas no meu caso o link simbólico deve apontar para um caminho relativo, então escrevi este script python para executar 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.

filipenf
fonte
1

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

#!/bin/bash
fdupes -r -1 CHANGE_THIS_PATH | sed -e 's/\(\w\) /\1|/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 isso faz é encontrar dupes e gravá-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 que não são o primeiro 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 a qualquer momento, mas dessa maneira parece mais claro.

David Ventura
fonte
2
Esta versão trata de arquivos com nomes que contêm um pipe? Suponho que nenhuma versão lida com nomes de arquivos contendo novas linhas, mas isso é uma limitação dos fdupes, em vez de qualquer outra coisa.
dhag
Não, mas você pode definir o IFS para o que quiser (também modificar o valor na substituição sed) e não deverá ter nenhum problema (IFS para 'ñ' ou algo assim deve funcionar)
David Ventura
Isso cria links simbólicos quebrados, e eu tenho arquivos vinculados a eles mesmos. NÃO USE
MrMesees
0

Algumas advertências na frente:

  • Específico do BASH
  • Não há espaço nos nomes dos arquivos
  • Assume que cada linha contém 2 arquivos no máximo.

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 2 arquivos forem duplicados (por exemplo, arquivo1 arquivo2 arquivo3), precisamos criar um link simbólico para cada par - trate arquivo1, arquivo2 e arquivo1, arquivo3 como 2 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 manipular automaticamente um número arbitrário de duplicatas por linha exigirá um pouco mais de esforço.

Outra abordagem seria criar primeiro links simbólicos para caminhos absolutos e depois 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 .

Isso é baseado na resposta de @Gilles: /unix//a/100955/77319

Dani_l
fonte