如何使用 python 用硬链接替换重复文件?
Posted
技术标签:
【中文标题】如何使用 python 用硬链接替换重复文件?【英文标题】:How to replace duplicate files with hard links using python? 【发布时间】:2013-06-19 04:57:35 【问题描述】:我是一名摄影师,做很多备份。多年来,我发现自己拥有很多硬盘。现在我买了一台 NAS 并使用 rsync 在一个 3TB raid 1 上复制了我所有的照片。根据我的脚本,这些文件中有大约 1TB 是重复的。这来自于在我的笔记本电脑上删除文件之前进行多次备份并且非常混乱。我确实在旧硬盘上备份了所有这些文件,但如果我的脚本把事情搞砸了,那就太痛苦了。您能否看看我的重复查找脚本并告诉我您是否认为我可以运行它?我在一个测试文件夹上试了一下,看起来还可以,但我不想在 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"
你怎么看? 非常感谢。
*如果这很重要:它是 Synology 713+ 并且具有 ext3 或 ext4 文件系统。
【问题讨论】:
而不是立即删除将重复项移动到另一个文件夹,然后在您满意没有丢失任何内容时将它们全部删除。 很遗憾 3TB NAS 已满。我只剩下 20GB,所以我必须删除它。此外,我说的是 139.020 个重复文件。我无法手动控制脚本没有出错。 @JasonTS:将文件移动到同一文件系统上的另一个目录不会浪费任何空间,创建 128K 硬链接将浪费大约 1 兆字节(可能少于您的shelve
数据库),因此可能不是拒绝嫌疑人建议的好理由。
同时,我认为这个问题属于 Code Review,而不是 Stack Overflow。
@abarnert:啊,抱歉,我想到了一个副本。那可能很好。但是我很快就需要空间,所以我真的认为我没有足够的时间来看看是否有问题。谢谢你的提示。我也在 Code Review 中发布了它。
【参考方案1】:
为什么不逐字节比较文件而不是第二个校验和?十亿分之一的校验和可能会意外匹配,但直接比较不应该失败。它不应该更慢,甚至可能更快。当有两个以上的文件并且您必须相互读取原始文件时,它可能会更慢。如果你真的想要,你可以通过一次比较所有文件的块来解决这个问题。
编辑:
我认为它不需要更多代码,只是不同而已。循环体是这样的:
data1 = fh1.read(8192)
data2 = fh2.read(8192)
if data1 != data2: return False
【讨论】:
否则我觉得没问题,但我不熟悉你使用的所有 API。我正在对他们的工作进行有根据的猜测。【参考方案2】:这看起来不错,经过一番清理(使其与 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))
这可确保在发生电源故障或其他类似错误时,不会丢失任何文件,但会留下尾随的“删除”。恢复脚本应该很简单。
【讨论】:
【参考方案3】:注意:如果您不喜欢 Python,那么现有的工具可以为您完成繁重的工作:
https://unix.stackexchange.com/questions/3037/is-there-an-easy-way-to-replace-duplicate-files-with-hardlinks
【讨论】:
【参考方案4】:如何创建硬链接。
在 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
当它工作时,你能发布你的整个代码吗?我也想用。
【讨论】:
谢谢!我决定不使用软链接,因为我不知道文件实际应该在哪里。稍后我会尝试手动整理。但现在我真的需要空间。通过硬链接,我将删除哪个“文件”并不重要。但是使用软链接,如果我想保留数据,我只能删除“真实文件”。另外我认为我的一些照片编辑软件不喜欢软链接。但我认为你是对的,创建链接可能会失败,我应该在失败时设置一个例外。我不需要使用 sudo,因为我以 root 身份运行。除了上面的照片,什么都没有。 软链接在这种情况下很危险,因为删除一个备份会破坏包含相同文件的所有其他备份。您也不需要需要以 root 身份运行来创建硬链接。以上是关于如何使用 python 用硬链接替换重复文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何删除python中的打印文本并将其替换为另一个文本[重复]