Trovare file duplicati e sostituirli con collegamenti simbolici


16

Sto cercando di trovare un modo per controllare all'interno di una determinata directory i file duplicati (anche con nomi diversi) e sostituirli con collegamenti simbolici che puntano alla prima occorrenza. Ho provato con fdupesma elenca solo quei duplicati.
Questo è il contesto: sto personalizzando un tema icona a mio piacimento, e ho scoperto che molte icone, anche se hanno nomi diversi e posizioni diverse all'interno della loro cartella genitore, e sono utilizzate per scopi diversi, sostanzialmente sono uguali immagine. Poiché applicare la stessa modifica venti o trenta volte è ridondante quando solo una è davvero necessaria, voglio mantenere solo un'immagine e collegare in modo simbolico tutte le altre.

Ad esempio, se corro fdupes -r ./all'interno della directory testdir, potrebbe restituirmi i seguenti risultati:

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

Dato questo output, vorrei mantenere solo il file file1.png, eliminare tutti gli altri e sostituirli con collegamenti simbolici che puntano su di esso, mantenendo tutti i nomi di file originali. Quindi file2.pngmanterrà il suo nome, ma diventerà un collegamento file1.pnginvece di essere un duplicato.

Tali collegamenti non dovrebbero puntare a un percorso assoluto, ma dovrebbero essere relativi alla testdirdirectory principale ; cioè yetanotherfile.pngsarà punto ../../file1.png, non a/home/testuser/.icons/testdir/file1.png

Sono interessato sia alle soluzioni che coinvolgono una GUI che alla CLI. Non è obbligatorio utilizzarlo fdupesL'ho citato perché è uno strumento che conosco, ma sono aperto a soluzioni che utilizzano anche altri strumenti.

Sono abbastanza sicuro che uno script bash per gestire tutto ciò non dovrebbe essere così difficile da creare, ma non sono abbastanza esperto da scoprire come scriverlo da solo.

Risposte:


3

Primo; C'è un motivo per cui è necessario utilizzare i symlink e non i soliti hardlink? Sto facendo fatica a capire la necessità di collegamenti simbolici con percorsi relativi. Ecco come risolverei questo problema:

Penso che la versione Debian (Ubuntu) di fdupes possa sostituire i duplicati con hard link usando l' -Lopzione, ma non ho un'installazione Debian per verificarlo.

Se non hai una versione con l' -Lopzione puoi usare questo piccolo script bash che ho trovato su commandlinefu .
Nota che questa sintassi funzionerà solo in 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

Il comando sopra troverà tutti i file duplicati in "percorso" e li sostituirà con hardlink. Puoi verificarlo eseguendo ls -ilRe guardando il numero dell'inode. Ecco un samle con dieci file identici:

$ 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

Tutti i file hanno numeri di inode separati, rendendoli file separati. Ora consente di deduplicarli:

$ 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

I file ora hanno tutti lo stesso numero di inode, il che significa che puntano tutti agli stessi dati fisici sul disco.

Spero che questo risolva il tuo problema o almeno ti punti nella giusta direzione!


Ho ricordato che fdupes aveva un'opzione per sostituire i duplicati con collegamenti, @arnefm ma non riesco a vedere nulla nell'uomo né è un'opzione in v1.51(Ubuntu 14.04.2 LTS).
Alastair,

Il mio fork jdupessu github.com/jbruchon/jdupes ha l' -Lopzione che fa l'hard link desiderato dei set duplicati.
Jody Lee Bruchon

Ho appena modificato la sceneggiatura qui. Non gestirà ancora gli spazi, ma gestirà altri caratteri speciali (nei file avevo stringhe di query URL). Inoltre, la ${line//…/}parte non funzionava per me, quindi ho fatto un modo più pulito per ottenere il primo file "master" su hardlink.
IBBoard

1
Avremmo bisogno dei softlink relativi se utilizzassimo rsyncun diverso tipo di file system? O se il file system non preserva la gerarchia, ad esempio è un server di backup che mette tutto sotto /«machine-name»/...? O se si desidera ripristinare dal backup? Non riesco a vedere come i collegamenti fisici verranno conservati qui. I softlink relativi avrebbero maggiori possibilità di sopravvivere, potrei pensare.
Amico,

6

Se non ti piacciono molto gli script, allora posso consigliare rdfind . Che eseguirà la scansione di determinate directory alla ricerca di file duplicati e li collegherà con hard link o soft. L'ho usato per deduplicare la mia directory di gemme di Ruby con grande successo. È disponibile in Debian / Ubuntu.


4

Ho avuto una situazione simile, ma nel mio caso il collegamento simbolico dovrebbe puntare a un percorso relativo, quindi ho scritto questo script Python per fare il trucco:

#!/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)

Per ogni riga di input (che è un elenco di file) lo script divide l'elenco dei file (spazi separati), ottiene il percorso relativo da ciascun file al primo e quindi crea il collegamento simbolico.


1

Quindi, la risposta data da arnefm (che è stata copiata su Internet) non si occupa degli spazi nei nomi dei file. Ho scritto una sceneggiatura che si occupa di spazi nei file.

#!/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

Quello che fa è trovare i duplicati e scriverli PIPE separati in un file chiamato 'files'.

Quindi legge il file indietro, riga per riga, in un array e ogni elemento dell'array viene delimitato dal PIPE.

Quindi scorre su tutti i non primi elementi dell'array, sostituendo il file con un collegamento simbolico al primo elemento.

Il file esterno ('files') potrebbe essere rimosso, se il comando fdupes viene eseguito in una subshell, che viene letto direttamente nel frattempo, ma in questo modo sembra più chiaro.


2
Questa versione si occupa di file con nomi contenenti una pipe? Suppongo che nessuna versione gestisca nomi di file contenenti newline, ma questa è una limitazione di fdupes piuttosto che altro.
dhag,

Non funziona, ma puoi impostare IFS come preferisci (modifica anche il valore nella sostituzione sed), quindi non dovresti avere alcun problema (IFS su 'ñ' o qualcosa del genere dovrebbe funzionare)
David Ventura

Questo crea collegamenti simbolici interrotti e ho file collegati a se stessi. NON USARE
MrMesees,

0

Alcuni avvertimenti:

  • BASH specifico
  • Nessuno spazio nei nomi dei file
  • Presuppone che ogni riga contenga al massimo 2 file.

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

Se sono duplicati più di 2 file (ad es. File1 file2 file3) di quanto sia necessario creare un collegamento simbolico per ogni coppia - trattare file1, file2 e file1, file3 come 2 casi separati:

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

Spendere questo per gestire automaticamente un numero arbitrario di duplicati per riga richiederà un po 'più di sforzo.

Un altro approccio sarebbe innanzitutto creare collegamenti simbolici in percorsi assoluti, quindi convertirli:

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 .

Questo si basa sulla risposta di @Gilles: /unix//a/100955/77319

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.