我编写了一个基本程序来检查包含许多 jpeg 文件(500000+)的目录树,验证它们没有损坏(大约 3-5% 的文件似乎以某种方式损坏)然后取一个 sha1sum文件(甚至是损坏的文件)并将信息保存到数据库中。
有问题的 jpeg 文件位于 windows 系统上,并通过 cifs 安装在 linux 机器上。它们的大小大多约为 4 兆字节,尽管有些可能稍大或稍小。
当我运行该程序时,它似乎运行得相当好一段时间,然后它因以下错误而崩溃。这是在它处理了大约 1100 个文件之后(错误表明在尝试打开 4.5 兆的文件时出现了问题)。
现在我明白我可以捕捉到这个错误并继续或重试等,但我很好奇它为什么会首先发生以及捕捉和重试是否真的可以解决问题 - 或者它会在重试时卡住(除非我当然限制重试,但随后会跳过一个文件)。
我在 debian 系统上使用“Python 2.7.5+”来运行它。系统至少有 4 Gig(可能是 8 个)的 ram,并且 top 报告脚本在运行时的任何时候都使用不到 1% 的 ram 和不到 3% 的 cpu。同样,此脚本运行的 jpeginfo 也使用同样少量的内存和 cpu。
为了避免在读取文件时使用过多的内存,我采用了这个答案中给出的方法来回答另一个问题:https ://stackoverflow.com/a/1131255/289545
此外,您可能会注意到“jpeginfo”命令处于等待“[OK]”响应的 while 循环中。这是因为如果“jpeginfo”认为它找不到文件,它会返回 0,因此 subprocess.check_output 调用不会将其视为错误状态。
我确实想知道 jpeginfo 在第一次尝试时似乎无法找到某些文件的事实是否可能相关(我怀疑它是相关的)但返回的错误说无法分配内存而不是找不到文件。
错误:
Traceback (most recent call last):
File "/home/m3z/jpeg_tester", line 95, in <module>
main()
File "/home/m3z/jpeg_tester", line 32, in __init__
self.recurse(self.args.dir, self.scan)
File "/home/m3z/jpeg_tester", line 87, in recurse
cmd(os.path.join(root, name))
File "/home/m3z/jpeg_tester", line 69, in scan
with open(filepath) as f:
IOError: [Errno 12] Cannot allocate memory: '/path/to/file name.jpg'
完整的程序代码:
1 #!/usr/bin/env python
2
3 import os
4 import time
5 import subprocess
6 import argparse
7 import hashlib
8 import oursql as sql
9
10
11
12 class main:
13 def __init__(self):
14 parser = argparse.ArgumentParser(description='Check jpeg files in a given directory for errors')
15 parser.add_argument('dir',action='store', help="absolute path to the directory to check")
16 parser.add_argument('-r, --recurse', dest="recurse", action='store_true', help="should we check subdirectories")
17 parser.add_argument('-s, --scan', dest="scan", action='store_true', help="initiate scan?")
18 parser.add_argument('-i, --index', dest="index", action='store_true', help="should we index the files?")
19
20 self.args = parser.parse_args()
21 self.results = []
22
23 if not self.args.dir.startswith("/"):
24 print "dir must be absolute"
25 quit()
26
27 if self.args.index:
28 self.db = sql.connect(host="localhost",user="...",passwd="...",db="fileindex")
29 self.cursor = self.db.cursor()
30
31 if self.args.recurse:
32 self.recurse(self.args.dir, self.scan)
33 else:
34 self.scan(self.args.dir)
35
36 if self.db:
37 self.db.close()
38
39 for line in self.results:
40 print line
41
42
43
44 def scan(self, dirpath):
45 print "Scanning %s" % (dirpath)
46 filelist = os.listdir(dirpath)
47 filelist.sort()
48 total = len(filelist)
49 index = 0
50 for filen in filelist:
51 if filen.lower().endswith(".jpg") or filen.lower().endswith(".jpeg"):
52 filepath = os.path.join(dirpath, filen)
53 index = index+1
54 if self.args.scan:
55 try:
56 procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip()
57 while "[OK]" not in procresult:
58 time.sleep(0.5)
59 print "\tRetrying %s" % (filepath)
60 procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip()
61 print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,procresult)
62 except subprocess.CalledProcessError, e:
63 os.renames(filepath, os.path.join(dirpath, "dodgy",filen))
64 filepath = os.path.join(dirpath, "dodgy", filen)
65 self.results.append("Trouble with: %s" % (filepath))
66 print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,e.output.strip())
67 if self.args.index:
68 sha1 = hashlib.sha1()
69 with open(filepath) as f:
70 while True:
71 data = f.read(8192)
72 if not data:
73 break
74 sha1.update(data)
75 sqlcmd = ("INSERT INTO `index` (`sha1`,`path`,`filename`) VALUES (?, ?, ?);", (buffer(sha1.digest()), dirpath, filen))
76 self.cursor.execute(*sqlcmd)
77
78
79 def recurse(self, dirpath, cmd, on_files=False):
80 for root, dirs, files in os.walk(dirpath):
81 if on_files:
82 for name in files:
83 cmd(os.path.join(root, name))
84 else:
85 cmd(root)
86 for name in dirs:
87 cmd(os.path.join(root, name))
88
89
90
91
92
93
94 if __name__ == "__main__":
95 main()