Поиск дубликатов файлов и замена их символическими ссылками

16

Я пытаюсь найти способ проверить в заданном каталоге дубликаты файлов (даже с разными именами) и заменить их символическими ссылками, указывающими на первое вхождение. Я пытался с, fdupesно он просто перечисляет эти дубликаты.
Это контекст: я настраиваю тему значков по своему вкусу, и я обнаружил, что многие значки, даже если они имеют разные имена и разные местоположения в родительской папке и используются для разных целей, в основном одинаковы картина. Поскольку применение одной и той же модификации двадцать или тридцать раз является излишним, когда действительно необходим только один, я хочу оставить только одно изображение и дать ссылку на все остальные.

Например, если я запускаю fdupes -r ./внутри каталога testdir, он может вернуть мне следующие результаты:

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

Учитывая этот вывод, я хотел бы сохранить только файл file1.png, удалить все остальные и заменить их символическими ссылками, указывающими на него, сохраняя при этом все исходные имена файлов. Таким образом file2.png, сохранит свое имя, но file1.pngвместо ссылки станет ссылкой на него.

Эти ссылки не должны указывать на абсолютный путь, но должны быть относительно родительского testdirкаталога; т.е. yetanotherfile.pngбудет указывать на ../../file1.png, а не на/home/testuser/.icons/testdir/file1.png

Я заинтересован как в решениях, которые включают GUI и CLI. Необязательно использовать, fdupesя процитировал это, потому что это инструмент, который я знаю, но я открыт для решений, которые также используют другие инструменты.

Я почти уверен, что bash-скрипт для обработки всего этого не должен быть таким сложным для создания, но я не достаточно опытен, чтобы самому понять, как его написать.

Sekhemty
источник

Ответы:

3

Первый; Есть ли причина, по которой вам нужно использовать символические ссылки, а не обычные жесткие ссылки? Мне трудно понять необходимость символических ссылок с относительными путями. Вот как бы я решил эту проблему:

Я думаю, что версия fdupes для Debian (Ubuntu) может заменить дубликаты жесткими ссылками, используя эту -Lопцию, но у меня нет установки Debian, чтобы это проверить.

Если у вас нет версии с -Lопцией, вы можете использовать этот крошечный скрипт bash, который я нашел в командной строкеfu .
Обратите внимание, что этот синтаксис будет работать только в 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

Приведенная выше команда найдет все дубликаты файлов в «пути» и заменит их на жесткие ссылки. Вы можете проверить это, запустив ls -ilRи посмотрев на номер инода. Вот пример с десятью одинаковыми файлами:

$ 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

Все файлы имеют отдельные номера инодов, что делает их отдельными файлами. Теперь давайте дедуплицируем их:

$ 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

Все файлы теперь имеют одинаковый номер инода, то есть все они указывают на одни и те же физические данные на диске.

Я надеюсь, что это решит вашу проблему или, по крайней мере, укажет вам правильное направление!

arnefm
источник
Я вспомнил, что у fdupes есть возможность заменить дупс ссылками, @arnefm, но я ничего не вижу в человеке, и при этом это не вариант v1.51(Ubuntu 14.04.2 LTS).
Аластер
У моего форка jdupesна github.com/jbruchon/jdupes есть -Lопция, которая делает желаемое жесткое связывание дублирующих наборов.
Джоди Ли Брухон
Я только что подправил сценарий здесь. Он по-прежнему не будет обрабатывать пробелы, но будет обрабатывать другие специальные символы (у меня были строки URL-запросов в файлах). Кроме того, эта ${line//…/}часть не работала для меня, поэтому я сделал более чистый способ получить первый «главный» файл для hardlink.
IBBoard
1
Нужны ли нам относительные программные ссылки, если мы используем rsyncдругую файловую систему? Или, если файловая система не сохраняет иерархию, например, это резервный сервер, который помещает все под /«machine-name»/...? Или если вы хотите восстановить из резервной копии? Я не вижу, как здесь будут сохраняться жесткие ссылки. Я думаю, что относительные мягкие ссылки имели бы больше шансов на выживание.
Приятель
6

Если вам не нравятся скрипты, я могу порекомендовать rdfind . Который будет сканировать заданные каталоги на наличие дубликатов файлов и жестко или мягко связывать их вместе. Я использовал его для дедупликации моего каталога Ruby gems с большим успехом. Это доступно в Debian / Ubuntu.

Андрей Франция
источник
4

У меня была похожая ситуация, но в моем случае символическая ссылка должна указывать на относительный путь, поэтому я написал этот скрипт на Python, чтобы добиться цели:

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

Для каждой строки ввода (которая представляет собой список файлов) скрипт разбивает список файлов (разделенных пробелами), получает относительный путь от каждого файла к первому и затем создает символическую ссылку.

filipenf
источник
1

Таким образом, ответ, данный arnefm (который был скопирован по всему Интернету), не касается пробелов в именах файлов. Я написал скрипт, который работает с пробелами в файлах.

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

Для этого нужно найти дупы и записать их PIPE, разделенные в файл с именем 'files'.

Затем он читает файл обратно, строка за строкой, в массив, и каждый элемент массива отделяется PIPE.

Затем он перебирает все не первые элементы массива, заменяя файл символической ссылкой на первый элемент.

Внешний файл ('files') может быть удален, если команда fdupes выполняется в подоболочке, которая читается напрямую в это время, но этот способ кажется более понятным.

Дэвид Вентура
источник
2
Эта версия имеет дело с файлами с именами, содержащими канал? Я предполагаю, что ни одна из версий не обрабатывает имена файлов, содержащие переводы строк, но это ограничение fdupes, а не что-то еще.
дхаг
Это не так, но вы можете установить IFS на то, что вы хотите (также измените значение в замене sed), тогда у вас не должно быть никаких проблем (IFS на '-' или что-то подобное должно работать)
David Ventura
Это создает битые символические ссылки, и у меня есть файлы, связанные с собой. НЕ ИСПОЛЬЗОВАТЬ
MrMesees
0

Некоторые предостережения:

  • BASH специфичный
  • Нет места в именах файлов
  • Предполагается, что каждая строка содержит максимум 2 файла.

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

Если более 2 файлов являются дубликатами (например, file1 file2 file3), то нам нужно создать символическую ссылку для каждой пары - рассматривайте file1, file2 и file1, file3 как 2 отдельных случая:

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

Чтобы автоматически обработать произвольное количество дубликатов в строке, потребуется немного больше усилий.

Другой подход заключается в том, чтобы сначала создать символические ссылки на абсолютные пути, а затем преобразовать их:

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 .

Это основано на ответе @Gilles: /unix//a/100955/77319

Dani_l
источник