Як замінити дублікати файлів на жорсткі посилання за допомогою python?

Я фотограф і робив багато резервних копій. Протягом багатьох років я опинився з великою кількістю жорстких дисків. Тепер я купив NAS і скопіював всі мої фотографії на один рейт 1 3 ТБ, використовуючи rsync. Згідно з моїм сценарієм близько 1 Тб цих файлів є дублікатами. Це пов'язано з виконанням декількох резервних копій, перш ніж видаляти файли на моєму ноутбуці та бути дуже брудними. У мене є резервна копія всіх цих файлів на старих жорстких дисках, але це було б болем, якщо мій скрипт попрямує. Чи можете ви, будь ласка, поглянути на мою дублікат пошуку сценарію і скажіть мені, якщо ви думаєте, я можу запустити це чи ні? Я спробував це на тестовій папці, і це виглядає добре, але я не хочу, щоб переполох на NAS.

Сценарій має три етапи у трьох файлах. У цій першій частині я знаходжу всі файли зображень та метаданих і розміщую їх у базі даних (datenbank) з їх розміром як ключовим.

import os
import shelve

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step1"), flag='c', protocol=None, writeback=False)

#path_to_search = os.path.join(os.path.dirname(__file__),"test")
path_to_search = "/volume1/backup_2tb_wd/"
file_exts = ["xmp", "jpg", "JPG", "XMP", "cr2", "CR2", "PNG", "png", "tiff", "TIFF"]
walker = os.walk(path_to_search)

counter = 0

for dirpath, dirnames, filenames in walker:
  if filenames:
    for filename in filenames:
      counter += 1
      print str(counter)
      for file_ext in file_exts:
        if file_ext in filename:
          filepath = os.path.join(dirpath, filename)
          filesize = str(os.path.getsize(filepath))
          if not filesize in datenbank:
            datenbank[filesize] = []
          tmp = datenbank[filesize]
          if filepath not in tmp:
            tmp.append(filepath)
            datenbank[filesize] = tmp

datenbank.sync()
print "done"
datenbank.close()

Друга частина. Тепер я скинув всі розміри файлів, які мають лише один файл у своєму списку, і створити іншу базу даних для полиць з хеш-пам'яттю md5 як ключем, а список файлів - як значення.

import os
import shelve
import hashlib

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step1"), flag='c', protocol=None, writeback=False)

datenbank_step2 = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step2"), flag='c', protocol=None, writeback=False)

counter = 0
space = 0

def md5Checksum(filePath):
    with open(filePath, 'rb') as fh:
        m = hashlib.md5()
        while True:
            data = fh.read(8192)
            if not data:
                break
            m.update(data)
        return m.hexdigest()


for filesize in datenbank:
  filepaths = datenbank[filesize]
  filepath_count = len(filepaths)
  if filepath_count > 1:
    counter += filepath_count -1
    space += (filepath_count -1) * int(filesize)
    for filepath in filepaths:
      print counter
      checksum = md5Checksum(filepath)
      if checksum not in datenbank_step2:
        datenbank_step2[checksum] = []
      temp = datenbank_step2[checksum]
      if filepath not in temp:
        temp.append(filepath)
        datenbank_step2[checksum] = temp

print counter
print str(space)

datenbank_step2.sync()
datenbank_step2.close()
print "done"

І, нарешті, найнебезпечніша частина. Для клавіатури evrey md5 я отримую список файлів і роблю додатковий sha1. Якщо він збігається, я видаляю кожен файл у цьому списку, вийшов з першого і створив жорсткий зв'язок для заміни видалених файлів.

import os
import shelve
import hashlib

datenbank = shelve.open(os.path.join(os.path.dirname(__file__),"shelve_step2"), flag='c', protocol=None, writeback=False)

def sha1Checksum(filePath):
    with open(filePath, 'rb') as fh:
        m = hashlib.sha1()
        while True:
            data = fh.read(8192)
            if not data:
                break
            m.update(data)
        return m.hexdigest()

for hashvalue in datenbank:
  switch = True
  for path in datenbank[hashvalue]:
    if switch:
      original = path
      original_checksum = sha1Checksum(path)
      switch = False
    else:
      if sha1Checksum(path) == original_checksum:
        os.unlink(path)
        os.link(original, path)
        print "delete: ", path
print "done"

Як ти гадаєш? Велике спасибі.

* якщо це як-то важливо: це синнологія 713+ і має файлову систему ext3 або ext4.

3
Переміщення також дуже швидко порівняно з копіями, так що ... Ви впевнені у вас не вистачає часу? (До речі, я думаю, що я фактично створив би все паралельне дерево, щоб перемістити їх, замість того, щоб перемістити їх у єдину плоску директорію. По-перше, каталог з 128K файлами в ньому може викликати проблеми (для файлової системи для вашого оболонка, для вашого сценарію Python та ін.) По-друге, навіть якщо ви втратите всі метадані в базі даних, паралельне дерево зроблять його тривіальним, щоб скасувати.)
додано Автор abarnert, джерело
Переміщення також дуже швидко порівняно з копіями, так що ... Ви впевнені у вас не вистачає часу? (До речі, я думаю, що я фактично створив би все паралельне дерево, щоб перемістити їх, замість того, щоб перемістити їх у єдину плоску директорію. По-перше, каталог з 128K файлами в ньому може викликати проблеми (для файлової системи для вашого оболонка, для вашого сценарію Python та ін.) По-друге, навіть якщо ви втратите всі метадані в базі даних, паралельне дерево зроблять його тривіальним, щоб скасувати.)
додано Автор abarnert, джерело
Тим часом, я думаю, що це питання належить Код огляду , а не стек переповнення.
додано Автор abarnert, джерело
Тим часом, я думаю, що це питання належить Код огляду , а не стек переповнення.
додано Автор abarnert, джерело
@JasonDS: Переміщення файлів в інший каталог на тій же файловій системі не втрачає жодного простору, і створення 128К жорстких зв'язків буде витрачати мегабайт або близько того (можливо, менше, ніж ваша база даних shelve ), тому, ймовірно, т є підставою для відхилення пропозиції підозрілого.
додано Автор abarnert, джерело
а не видалити, негайно перемістіть дублікати в іншу папку, потім видаліть їх усім, коли ви не задоволені, нічого не втрачено.
додано Автор suspectus, джерело
а не видалити, негайно перемістіть дублікати в іншу папку, потім видаліть їх усім, коли ви не задоволені, нічого не втрачено.
додано Автор suspectus, джерело
@abarnert: Ах, вибач, я думав про копію. Добре це може бути красиво. Але мені незабаром потрібен простір, тому я дійсно не думаю, що маю достатньо часу, щоб побачити, чи є щось погане чи ні. Дякую за відгук. Я також розмістив його в огляді коду.
додано Автор JasonTS, джерело
@abarnert: Ах, вибач, я думав про копію. Добре це може бути красиво. Але мені незабаром потрібен простір, тому я дійсно не думаю, що маю достатньо часу, щоб побачити, чи є щось погане чи ні. Дякую за відгук. Я також розмістив його в огляді коду.
додано Автор JasonTS, джерело
Ну, я маю час перенести їх в інший каталог. Але якщо це не збій дуже очевидно, я не думаю, що є спосіб перевірити всі папки вручну. І оскільки я дійсно потребую звільнити свій ноутбук, мені доведеться видалити папку, яку я перемістив у будь-якому випадку. Але крім цього. Ви бачите будь-які помилки в моєму коді? Або ви думаєте, що це має працювати?
додано Автор JasonTS, джерело
Ну, я маю час перенести їх в інший каталог. Але якщо це не збій дуже очевидно, я не думаю, що є спосіб перевірити всі папки вручну. І оскільки я дійсно потребую звільнити свій ноутбук, мені доведеться видалити папку, яку я перемістив у будь-якому випадку. Але крім цього. Ви бачите будь-які помилки в моєму коді? Або ви думаєте, що це має працювати?
додано Автор JasonTS, джерело
На жаль, 3ТБ NAS повна. У мене залишилося лише 20 Гб, тому я повинен його видалити. Крім того, я говорю про 139,020 дубльованих файлів. Я не можу вручну керувати тим, що сценарій не зламав.
додано Автор JasonTS, джерело

8 Відповіді

Це добре виглядало, і трохи дезинфікуючи (щоб він працював з python 3.4), я побіг це на моєму NAS. У той час як у мене були жорсткі посилання на файли, які не були змінені між резервними копіями, файли, які були переміщені, були дубльовані. Це відновило те, що втратив дисковий простір для мене.

Невелика нітпік - це те, що файли, які вже є жорсткими посиланнями, видаляються та змінюються. Це ніяк не впливає на кінцевий результат.

Я зробив трохи змінити третій файл ("3.py"):

if sha1Checksum(path) == original_checksum:
     tmp_filename = path + ".deleteme"
     os.rename(path, tmp_filename)
     os.link(original, path)
     os.unlink(tmp_filename)
     print("Deleted {} ".format(path))

Це гарантує, що у разі несправності живлення чи якоїсь іншої подібної помилки, жоден файл не буде втрачено, хоча залишкове "deleteme" залишається позаду. Сценарій відновлення має бути досить тривіальним.

1
додано

Це добре виглядало, і трохи дезинфікуючи (щоб він працював з python 3.4), я побіг це на моєму NAS. У той час як у мене були жорсткі посилання на файли, які не були змінені між резервними копіями, файли, які були переміщені, були дубльовані. Це відновило те, що втратив дисковий простір для мене.

Невелика нітпік - це те, що файли, які вже є жорсткими посиланнями, видаляються та змінюються. Це ніяк не впливає на кінцевий результат.

Я зробив трохи змінити третій файл ("3.py"):

if sha1Checksum(path) == original_checksum:
     tmp_filename = path + ".deleteme"
     os.rename(path, tmp_filename)
     os.link(original, path)
     os.unlink(tmp_filename)
     print("Deleted {} ".format(path))

Це гарантує, що у разі несправності живлення чи якоїсь іншої подібної помилки, жоден файл не буде втрачено, хоча залишкове "deleteme" залишається позаду. Сценарій відновлення має бути досить тривіальним.

1
додано

Чому б не порівнювати байт файлів для байтів замість другої контрольної суми? Одна з мільярдів двох контрольних сум може випадково зіставлятись, але пряме порівняння не повинно закінчуватися. Це не повинно бути повільним і може бути навіть швидшим. Може бути, це може бути повільніше, коли існує більше двох файлів, і вам слід прочитати оригінальний файл один для одного. Якщо ви дійсно хотіли б, ви могли б обійти це, порівнюючи блоки всіх файлів одночасно.

EDIT:

Я не думаю, що це вимагатиме більше коду, просто інше. Щось подібне для тіла циклу:

data1 = fh1.read(8192)
data2 = fh2.read(8192)
if data1 != data2: return False
1
додано
В іншому випадку це виглядає добре для мене, але я не знайомі з усіма використовуваними вами API. Я роблю уявлення про те, що вони роблять.
додано Автор morningstar, джерело

Чому б не порівнювати байт файлів для байтів замість другої контрольної суми? Одна з мільярдів двох контрольних сум може випадково зіставлятись, але пряме порівняння не повинно закінчуватися. Це не повинно бути повільним і може бути навіть швидшим. Може бути, це може бути повільніше, коли існує більше двох файлів, і вам слід прочитати оригінальний файл один для одного. Якщо ви дійсно хотіли б, ви могли б обійти це, порівнюючи блоки всіх файлів одночасно.

EDIT:

Я не думаю, що це вимагатиме більше коду, просто інше. Щось подібне для тіла циклу:

data1 = fh1.read(8192)
data2 = fh2.read(8192)
if data1 != data2: return False
1
додано
В іншому випадку це виглядає добре для мене, але я не знайомі з усіма використовуваними вами API. Я роблю уявлення про те, що вони роблять.
додано Автор morningstar, джерело

Як ви створюєте жорсткий зв'язок.

У Linux ви робите

sudo ln sourcefile linkfile

Іноді це може вийти з ладу (для мене іноді це не вдається). Крім того, ваш скрипт python повинен запускатися в режимі судо.

Тому я використовую символічні посилання:

ln -s sourcefile linkfile

I can check for them with os.path.islink

Ви можете викликати такі команди в Python:

os.system("ln -s sourcefile linkfile")

або подібне, використовуючи підпроцесор :

import subprocess
subprocess.call(["ln", "-s", sourcefile, linkfile], shell = True)

Have a look at execution from command line and hard vs. soft links

Коли він працює, чи зможете ви опублікувати весь свій код? Я також хотів би його використовувати.

0
додано
М'які посилання є небезпечними для цього сценарію, оскільки видалення одного резервного копіювання призведе до виходу всіх інших резервних копій, що містять один файл. Ви також не повинні запускатись як root, щоб створювати жорсткі посилання.
додано Автор WhyNotHugo, джерело
Дякую! Я вирішив проти програмних посилань, тому що я не знаю, де повинен бути файл. Я спробую попрацювати вручну пізніше. Але на сьогодні мені дуже потрібен простір. З жорстким посиланням не має значення, який "файл" я видам. Але за допомогою програмних посилань я можу видалити лише "справжні файли", якщо хочу зберегти дані. Також я думаю, що деяке моє програмне забезпечення для редагування фотографій не хотіло б мати софт-посилання. Але я думаю, ви правильні, створення посилання може вийти з ладу, і я повинен виключити, коли це не вдасться. Мені не потрібно використовувати sudo, тому що я працюю як root. Тут немає нічого, окрім фотографій.
додано Автор JasonTS, джерело

Як ви створюєте жорсткий зв'язок.

У Linux ви робите

sudo ln sourcefile linkfile

Іноді це може вийти з ладу (для мене іноді це не вдається). Крім того, ваш скрипт python повинен запускатися в режимі судо.

Тому я використовую символічні посилання:

ln -s sourcefile linkfile

I can check for them with os.path.islink

Ви можете викликати такі команди в Python:

os.system("ln -s sourcefile linkfile")

або подібне, використовуючи підпроцесор :

import subprocess
subprocess.call(["ln", "-s", sourcefile, linkfile], shell = True)

Have a look at execution from command line and hard vs. soft links

Коли він працює, чи зможете ви опублікувати весь свій код? Я також хотів би його використовувати.

0
додано
М'які посилання є небезпечними для цього сценарію, оскільки видалення одного резервного копіювання призведе до виходу всіх інших резервних копій, що містять один файл. Ви також не повинні запускатись як root, щоб створювати жорсткі посилання.
додано Автор WhyNotHugo, джерело
Дякую! Я вирішив проти програмних посилань, тому що я не знаю, де повинен бути файл. Я спробую попрацювати вручну пізніше. Але на сьогодні мені дуже потрібен простір. З жорстким посиланням не має значення, який "файл" я видам. Але за допомогою програмних посилань я можу видалити лише "справжні файли", якщо хочу зберегти дані. Також я думаю, що деяке моє програмне забезпечення для редагування фотографій не хотіло б мати софт-посилання. Але я думаю, ви правильні, створення посилання може вийти з ладу, і я повинен виключити, коли це не вдасться. Мені не потрібно використовувати sudo, тому що я працюю як root. Тут немає нічого, окрім фотографій.
додано Автор JasonTS, джерело

Примітка. Якщо ви не одружені на Python, існують потужні інструменти для важкого підйому:

https: //unix.stackexchange. com/questions/3037/є-там-простий спосіб-замінити-дублікати-файли-з-жорсткі посилання

0
додано

Примітка. Якщо ви не одружені на Python, існують потужні інструменти для важкого підйому:

https: //unix.stackexchange. com/questions/3037/є-там-простий спосіб-замінити-дублікати-файли-з-жорсткі посилання

0
додано
ІТ КПІ - Python
ІТ КПІ - Python
625 учасників

Канал обговорень про всякі штуки зі світу пайтону. Прохання: 0. мати повагу одне до одного; 1. не матюкатися в сторону людей; 2. не захламляти тред повідомленнями по одному слову;