How to replace duplicate files with hard links using python?
我是个摄影师,做很多备份。多年来,我发现自己有很多硬盘。现在我买了一个NAS,并使用rsync在一个3tbraid1上复制了我的所有图片。根据我的脚本,这些文件中大约有1TB是重复的。这是因为在删除笔记本电脑上的文件之前做了多次备份,而且非常混乱。我确实在旧硬盘上备份了所有这些文件,但如果我的脚本把事情搞砸了,那将是一件痛苦的事。你能看看我的重复查找脚本,告诉我你认为我能运行它吗?我在一个测试文件夹上试过了,看起来还可以,但我不想把NAS搞得一团糟。
脚本在三个文件中有三个步骤。在第一部分中,我找到了所有的图像和元数据文件,并将它们放在一个搁置数据库(datenbank)中,其大小作为键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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哈希为键,文件列表为值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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。如果匹配,我将删除该列表中除第一个文件外的所有文件,并创建一个硬链接来替换删除的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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文件系统。
这个看起来不错,在清理了一点之后(为了使它能与Python3.4一起工作),我在我的NAS上运行了这个。虽然我有备份之间未修改的文件的硬链接,但移动的文件将被复制。这为我恢复了丢失的磁盘空间。
一个小问题是已经硬链接的文件被删除并重新链接。无论如何,这不会影响最终结果。
我稍微修改了第三个文件("3.py"):
1 2 3 4 5 6 | 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 2 3 | data1 = fh1.read(8192) data2 = fh2.read(8192) if data1 != data2: return False |
注意:如果您不是与python结婚的,那么有一些现成的工具可以为您完成繁重的工作:
https://unix.stackexchange.com/questions/3037/is-there-an-easy-way-to-replace-duplicate-files-with-hardlinks
如何创建硬链接?
在Linux中
1 | sudo ln sourcefile linkfile |
有时这会失败(对我来说,有时会失败)。另外,python脚本需要以sudo模式运行。
所以我使用符号链接:
1 | ln -s sourcefile linkfile |
我可以用os.path.islink检查它们
您可以在python中调用如下命令:
1 | os.system("ln -s sourcefile linkfile") |
或者像这样使用子流程:
1 2 | import subprocess subprocess.call(["ln","-s", sourcefile, linkfile], shell = True) |
从命令行和硬链接与软链接中查看执行情况
当它工作的时候,你能把你的整个代码贴出来吗?我也想用它。