PythonでZIPファイルを展開する(コード例)
「PythonでZIPファイルを展開する(メモ)」の続き。
ここでは、前回のメモの内容を踏まえた上で作成した、ZIPファイルを展開するPythonのコード例を貼り付ける。ファイル名についてはWindows上で日本語を含むもの(エンコーディングがCP932な場合)にも対応した。
ただ、処理を正確に行うことを重視しているため、似たような処理を繰り返しているところがあり、もっとうまく書く方法があるかもしれない。また、unzipコマンドと異なり、パーミッション(属性)は復元しない。
その他、未知の不具合が存在する可能性もある。*1
[任意]ファイル名: unzip.py
#! /usr/bin/python # -*- coding: utf-8 -*- # ZIPファイルを展開するPythonスクリプト # CP932エンコーディングのファイル名を含むZIPファイルに対応/タイムスタンプ復元機能 # version 20101117 # (C) 2009-2010 kakurasan # Licensed under GPLv3+ from optparse import OptionParser from zipfile import ZipFile import locale import errno import time import sys import os def main (): # ロケール設定 # エラー発生時のメッセージのロケールに影響 locale.setlocale (locale.LC_ALL, '') # オプション解析(-dオプションで展開先指定を可能に) parser = OptionParser (usage='%prog ( -d ) [zipfile]') parser.set_defaults (outdir=os.getcwd ()) parser.add_option ('-d', '--output-directory', dest='outdir', action='store', type='string', help='set output directory', metavar='DIR') (options, args) = parser.parse_args () # 入力ファイルなし if len (args) < 1: parser.error ('no input file specified') # 入力ファイルと展開先接頭辞を決定 infile = args[0] prefix = options.outdir # 出力先ディレクトリが無ければ掘っておく try: os.makedirs (prefix) except: # 既にある場合と書き込み失敗の場合とがあるが # 失敗した場合は後でファイル書き込み時にもエラーが出るので一緒に扱うことにする pass # ZIPファイルを開いて展開 (filesl, dirsl) = ([], []) try: f_zip = ZipFile (infile, 'r') print '[OK] open: %s' % infile except IOError, (no, msg): print >> sys.stderr, '[NG] open: %s: IOError[%d]: %s' % (infile, no, msg) return 1 (namel, infol) = (f_zip.namelist (), f_zip.infolist ()) try: # finally try: # 分別 # namelist()の名前とinfolist()の情報は同じ順番なのでzip()で同時に回す for (item, info) in zip (namel, infol): # ZipInfoオブジェクトのメンバdate_timeは使いにくく # 文字列を介してtime.strptime()とtime.mktime()したものをos.utime()へ timestamp = time.mktime (time.strptime ('%d/%02d/%02d %02d:%02d:%02d' % info.date_time, '%Y/%m/%d %H:%M:%S')) if item.endswith ('/'): # ディレクトリ dirsl.append ((item, timestamp)) else: # ファイル filesl.append ((item, timestamp)) # ディレクトリのない書庫向けに親ディレクトリもディレクトリ一覧に追加 parent_in_list = False parent = item while True: # 階層を1つずつ上がっていき、一番上までたどったら抜ける parent = os.path.dirname (parent) if parent == '': break # 重複しないように、現在のディレクトリ一覧と照らし合わせて # ない場合にのみ追加する for (i, t) in dirsl: if parent == i: parent_in_list = True if not parent_in_list: dirsl.append ((parent, None)) # ディレクトリを先に作成 for (item, timestamp) in dirsl: try: item_unicode = item.decode ('utf-8') # UTF-8 except UnicodeDecodeError: try: item_unicode = item.decode ('cp932') # Win上の日本語ファイル/ディレクトリ名 except UnicodeDecodeError: item_unicode = item.decode ('ascii') outpath = os.path.join (prefix, item_unicode.encode ('utf-8')) try: os.makedirs (outpath) except OSError, (no, msg): # 存在することによる失敗は無視 if no != errno.EEXIST: print >> sys.stderr, '*NG* makedirs: %s: OSError[%d] %s' % (outpath, no, msg) return 1 # ファイルを展開 for (item, timestamp) in filesl: try: item_unicode = item.decode ('utf-8') except UnicodeDecodeError: try: item_unicode = item.decode ('cp932') except UnicodeDecodeError: item_unicode = item.decode ('ascii') outpath = os.path.join (prefix, item_unicode.encode ('utf-8')) try: f_out = open (outpath, 'wb') # バイナリモード指定必須 print '[OK] open: %s' % outpath except IOError, (no, msg): print >> sys.stderr, '[NG] open: %s: IOError[%d] %s' % (outpath, no, msg) return 1 try: # finally try: f_out.write (f_zip.read (item)) print '[OK] write: %s' % outpath except IOError, (no, msg): print >> sys.stderr, '[NG] write: %s: IOError[%d] %s' % (outpath, no, msg) return 1 finally: f_out.close () # タイムスタンプ設定 try: os.utime (outpath, (timestamp, timestamp)) print '[OK] utime: %d: ' % (timestamp) + outpath except OSError, (no, msg): print >> sys.stderr, '[NG] utime: %s: OSError[%d] %s' % (outpath, no, msg) return 1 # 最後にディレクトリのタイムスタンプを設定 for (item, timestamp) in dirsl: try: item_unicode = item.decode ('utf-8') except UnicodeDecodeError: try: item_unicode = item.decode ('cp932') except UnicodeDecodeError: item_unicode = item.decode ('ascii') outpath = os.path.join (prefix, item_unicode.encode ('utf-8')) if timestamp: try: os.utime (outpath, (timestamp, timestamp)) print '[OK] utime: %d: ' % (timestamp) + outpath except OSError, (no, msg): print >> sys.stderr, '[NG] utime: %s: OSError[%d] %s' % (outpath, no, msg) return 1 else: # ファイル一覧からこのディレクトリ中の項目の中で最新のものにする timestamp = 0 for (n, i) in zip (namel, infol): if n.startswith (item): t = time.mktime (time.strptime ('%d/%02d/%02d %02d:%02d:%02d' % i.date_time, '%Y/%m/%d %H:%M:%S')) if timestamp < t: timestamp = t try: os.utime (outpath, (timestamp, timestamp)) print '[OK] utime: %d: ' % (timestamp) + outpath except OSError, (no, msg): print >> sys.stderr, '[NG] utime: %s: OSError[%d] %s' % (outpath, no, msg) return 1 except IOError, (no, msg): print >> sys.stderr, '[NG] IOError[%d]: %s' % (no, msg) return 1 finally: f_zip.close() return 0 if __name__ == '__main__': sys.exit (main ())
(2010/11/17)ディレクトリのない書庫でエラーが出る不具合を修正・タイムスタンプ関係の処理を改善
関連記事:
*1:1つ見つけたのはChromiumのスナップショットのZIPファイルのタイムスタンプがずれることだが、原因も対処も不明