2013-06-21 4 views
3

Я фотограф и делаю много резервных копий. За эти годы я нашел себя с большим количеством жестких дисков. Теперь я купил NAS и скопировал все мои фотографии на одном рейде 3TB 1, используя rsync. По моему сценарию около 1 ТБ этих файлов дублируются. Это происходит от выполнения нескольких резервных копий, прежде чем удалять файлы на моем ноутбуке и быть очень грязными. У меня есть резервная копия всех этих файлов на старых жестких дисках, но было бы больно, если бы мой скрипт испортил вещи. Не могли бы вы взглянуть на мой дубликат сценария поиска и рассказать мне, думаете ли вы, что я могу запустить его или нет? Я попробовал его в тестовой папке, и все выглядит нормально, но я не хочу испортить NAS.Как заменить дубликаты файлов на жесткие ссылки с помощью python?

Скрипт состоит из трех шагов в трех файлах. В этой первой части я нахожу все файлы изображений и метаданных и помещаю их в базу данных полки (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.

+1

, а не удалять немедленно переместить дубликаты в другую папку, то удалите их всех, когда вы удовлетворены тем, что ничего не потеряно. – suspectus

+1

К сожалению, NAS 3TB полон. У меня осталось только 20 ГБ, поэтому я должен удалить его. Кроме того, я говорю о 139.020 дублированных файлах. Я не могу контролировать вручную, что скрипт не испортил. – JasonTS

+2

@JasonTS: Перемещение файлов в другой каталог в одной и той же файловой системе не будет тратить места, а создание жестких ссылок 128K будет тратить мегабайт или около того (вероятно, меньше, чем ваша база данных «shelve»), так что, вероятно, это не очень хорошая причина отклонить предложение подозреваемого. – abarnert

ответ

1

Почему бы не сравнить байт файлов для байта вместо второй контрольной суммы? Один из миллиардов контрольных сумм может случайно совпадать, но прямое сравнение не должно заканчиваться. Это не должно быть медленнее и может быть даже быстрее. Возможно, это может быть медленнее, когда файлов более двух, и вы должны прочитать исходный файл друг для друга. Если вы действительно хотели, вы могли бы обойти это, сравнив блоки всех файлов одновременно.

EDIT:

Я не думаю, что для этого потребуется больше кода, просто по-другому. Что-то вроде этого для корпуса петли:

data1 = fh1.read(8192) 
data2 = fh2.read(8192) 
if data1 != data2: return False 
+0

В противном случае это выглядит хорошо для меня, но я не знаком со всеми API-интерфейсами, которые вы использовали. Я делаю обоснованное предположение о том, что они делают. – morningstar

0

Как вы создаете жесткую ссылку.

В Linux вы

sudo ln sourcefile linkfile 

Иногда это может потерпеть неудачу (для меня это не удается иногда). Также ваш скрипт python должен работать в режиме sudo.

Поэтому я использую символические ссылки:

ln -s sourcefile linkfile 

я могу проверить их с os.path.islink

Можно назвать такие команды, как это в Python:

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

или как это с помощью subprocess :

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

Посмотрите execution from command line и hard vs. soft links

Когда он работает, не могли бы вы опубликовать весь код? Я тоже хотел бы использовать его.

+0

Спасибо! Я решил не использовать ссылки, потому что я не знаю, где именно должен быть файл. Я попытаюсь привести в порядок вручную позже. Но на данный момент мне действительно нужно пространство. С жесткой ссылкой не имеет значения, какой «файл» я удалю. Но с помощью мягких ссылок я могу удалить только «истинные файлы», если хочу сохранить данные. Также я думаю, что некоторые из моих программ для редактирования фотографий не понравятся мягкие ссылки. Но я думаю, что вы правы, создание ссылки может привести к сбою, и я должен сделать исключение, когда оно не удастся. Мне не нужно использовать sudo, потому что я выполняю роль root. Там нет ничего, кроме фотографий. – JasonTS

+0

Софт-ссылки опасны для этого сценария, поскольку удаление одной резервной копии приведет к поломке всех других резервных копий, содержащих один и тот же файл. Вы также ** не ** должны запускаться как root для создания жестких ссылок. – WhyNotHugo

1

Это выглядело хорошо, и после некоторой дезинфекции (чтобы он работал с python 3.4), я запускал это на своем NAS. Хотя у меня были жесткие ссылки на файлы, которые не были изменены между резервными копиями, файлы, которые были перемещены, дублировались. Это восстановилось, что потеряло место на диске для меня.

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

Я сделал немного изменить третий файл («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)) 

Это гарантирует, что в случае отказа питания или какой-либо другой подобной ошибки, никакие файлы не будут потеряны, хотя отстающий «dieeme» остается позади. Сценарий восстановления должен быть довольно тривиальным.