Znajdowanie zduplikowanych plików i zastępowanie ich dowiązaniami symbolicznymi

16

Próbuję znaleźć sposób na sprawdzenie w danym katalogu zduplikowanych plików (nawet o różnych nazwach) i zastąpienie ich dowiązaniami symbolicznymi wskazującymi na pierwsze wystąpienie. Próbowałem z, fdupesale wyświetla tylko te duplikaty.
Taki jest kontekst: dostosowuję motyw ikon do swoich upodobań i odkryłem, że wiele ikon, nawet jeśli mają różne nazwy i różne lokalizacje w swoim folderze nadrzędnym i są używane do różnych celów, w zasadzie są takie same obrazek. Ponieważ zastosowanie tej samej modyfikacji dwadzieścia lub trzydzieści razy jest zbędne, gdy tylko jeden jest naprawdę potrzebny, chcę zachować tylko jeden obraz i dowiązać symbolicznie wszystkie pozostałe.

Na przykład, jeśli uruchomię się fdupes -r ./w katalogu testdir, może zwrócić mi następujące wyniki:

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

Biorąc pod uwagę ten wynik, chciałbym zachować tylko plik file1.png, usunąć wszystkie pozostałe i zastąpić je dowiązaniami symbolicznymi wskazującymi do niego, zachowując jednocześnie wszystkie oryginalne nazwy plików. Zachowa więc file2.pngswoją nazwę, ale stanie się łączem do file1.pngzamiast być duplikatem.

Te linki nie powinny wskazywać bezwzględnej ścieżki, ale powinny być względne w stosunku do testdirkatalogu nadrzędnego ; tj. yetanotherfile.pngbędzie wskazywać ../../file1.png, a nie/home/testuser/.icons/testdir/file1.png

Interesuję się zarówno rozwiązaniami, które obejmują GUI i CLI. Cytowanie go nie jest obowiązkowe fdupes, ponieważ to narzędzie, które znam, ale jestem otwarty na rozwiązania wykorzystujące również inne narzędzia.

Jestem prawie pewien, że skrypt bash, który poradzi sobie z tym wszystkim, nie powinien być trudny do stworzenia, ale nie jestem wystarczająco ekspertem, aby dowiedzieć się, jak sam go napisać.

Sekhemty
źródło

Odpowiedzi:

3

Pierwszy; Czy istnieje powód, dla którego należy używać dowiązań symbolicznych, a nie zwykłych dowiązań twardych? Trudno mi zrozumieć potrzebę istnienia dowiązań symbolicznych ze ścieżkami względnymi. Oto jak rozwiązałbym ten problem:

Myślę, że wersja fdupes Debiana (Ubuntu) może zastąpić duplikaty twardymi linkami przy użyciu tej -Lopcji, ale nie mam instalacji Debiana, aby to sprawdzić.

Jeśli nie masz wersji z -Lopcją, możesz użyć tego małego skryptu bash, który znalazłem na linii poleceń .
Zauważ, że ta składnia będzie działać tylko w 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

Powyższe polecenie znajdzie wszystkie zduplikowane pliki w „ścieżce” i zastąpi je linkami twardymi. Możesz to sprawdzić, uruchamiając ls -ilRi sprawdzając numer i-węzła. Oto próbka z dziesięcioma identycznymi plikami:

$ 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

Wszystkie pliki mają osobne numery i-węzłów, co czyni je osobnymi plikami. Teraz deduplikujmy je:

$ 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

Wszystkie pliki mają teraz ten sam numer i-węzła, co oznacza, że ​​wszystkie wskazują te same fizyczne dane na dysku.

Mam nadzieję, że to rozwiąże Twój problem lub przynajmniej skieruje Cię w dobrym kierunku!

arnefm
źródło
Przypomniałem sobie, że fdupes ma opcję zastępowania duplikatów linkami @arnefm, ale nic nie widzę w tym człowieku, ani nie jest to opcja w v1.51(Ubuntu 14.04.2 LTS).
Alastair,
Mój widelec jdupesna github.com/jbruchon/jdupes ma -Lopcję, która wykonuje pożądane twarde łączenie duplikatów.
Jody Lee Bruchon
Właśnie poprawiłem skrypt tutaj. Nadal nie obsługuje spacji, ale obsługuje inne znaki specjalne (miałem ciągi zapytań URL w plikach). Ponadto ${line//…/}część nie działała dla mnie, więc zrobiłem czystszy sposób, aby uzyskać pierwszy plik „master” do hardlink.
IBBoard
1
Czy potrzebowalibyśmy względnych softlinków, jeśli korzystamy rsyncz innego rodzaju systemu plików? Lub jeśli system plików nie zachowuje hierarchii, np. Jest to serwer zapasowy, który wszystko podporządkowuje /«machine-name»/...? A jeśli chcesz przywrócić z kopii zapasowej? Nie widzę, jak twarde linki będą tutaj przechowywane. Względne linki miękkie miałyby większą szansę na przetrwanie, jak sądzę.
Buddy,
6

Jeśli nie lubisz dużo skryptów, mogę polecić rdfind . Który skanuje podane katalogi w poszukiwaniu duplikatów plików i łączy je na twardo lub miękko. Z powodzeniem wykorzystałem go do deduplikacji mojego katalogu klejnotów Ruby. Jest dostępny w Debian / Ubuntu.

Andrew France
źródło
4

Miałem podobną sytuację, ale w moim przypadku dowiązanie symboliczne powinno wskazywać na ścieżkę względną, więc napisałem ten skrypt Pythona, aby wykonać tę sztuczkę:

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

Dla każdego wiersza wejściowego (który jest listą plików) skrypt dzieli listę plików (oddzielone spacjami), pobiera ścieżkę względną z każdego pliku do pierwszego, a następnie tworzy dowiązanie symboliczne.

filipenf
źródło
1

Tak więc odpowiedź udzielona przez arnefm (która została skopiowana przez Internet) nie dotyczy spacji w nazwach plików. Napisałem skrypt, który zajmuje się spacjami w plikach.

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

To polega na znajdowaniu duplikatów i zapisywaniu ich PIPE rozdzielonych na plik o nazwie „pliki”.

Następnie odczytuje plik z powrotem, wiersz po wierszu, do tablicy, a każdy element tablicy jest rozdzielany przez PIPE.

Następnie iteruje wszystkie nie pierwsze elementy tablicy, zastępując plik dowiązaniem symbolicznym do pierwszego elementu.

Plik zewnętrzny („pliki”) można usunąć, jeśli polecenie fdupes jest wykonywane w podpowłoce, do tego czasu jest odczytywane bezpośrednio, ale w ten sposób wydaje się wyraźniejsze.

David Ventura
źródło
2
Czy ta wersja obsługuje pliki o nazwach zawierających potok? Zakładam, że żadna wersja nie obsługuje nazw plików zawierających nowe wiersze, ale jest to ograniczenie fdupes zamiast czegokolwiek innego.
dhag
Nie działa, ale możesz ustawić IFS na cokolwiek chcesz (także zmienić wartość w zamianie sed), wtedy nie powinieneś mieć żadnych problemów (IFS na „ñ” lub coś takiego powinno działać)
David Ventura
Tworzy to zepsute dowiązania symboliczne, a ja mam pliki połączone ze sobą. NIE UŻYWAJ
MrMesees
0

Niektóre zastrzeżenia z przodu:

  • Specyficzne dla BASH
  • Brak miejsca w nazwach plików
  • Zakłada, że ​​każda linia zawiera maksymalnie 2 pliki.

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

Jeśli więcej niż 2 pliki są duplikatami (np. Plik1 plik2 plik3), musimy utworzyć dowiązanie symboliczne dla każdej pary - traktuj plik1, plik2 i plik1, plik3 jako 2 osobne przypadki:

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

Wykorzystanie tej opcji do automatycznej obsługi dowolnej liczby duplikatów w wierszu wymaga nieco więcej wysiłku.

Innym podejściem byłoby najpierw utworzenie dowiązań symbolicznych do ścieżek bezwzględnych, a następnie ich konwersja:

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 .

Opiera się to na odpowiedzi @Gilles: /unix//a/100955/77319

Dani_l
źródło