У меня были проблемы с распаковкой tar
и zip
файлами, которые я получаю от пользователей Windows. Хотя я не отвечаю на вопрос «как создать архив, который будет работать», приведенные ниже скрипты помогают правильно распаковывать tar
и zip
файлы независимо от оригинальной ОС.
ВНИМАНИЕ: необходимо настроить кодировку источника вручную ( cp1251
, cp866
в примерах ниже). Параметры командной строки могут быть хорошим решением в будущем.
Деготь:
#!/usr/bin/env python import tarfile import codecs import sys def recover(name): return codecs.decode(name, 'cp1251') for tar_filename in sys.argv[1:]: tar = tarfile.open(name=tar_filename, mode='r', bufsize=16*1024) updated = [] for m in tar.getmembers(): m.name = recover(m.name) updated.append(m) tar.extractall(members=updated) tar.close()
Почтовый индекс:
#!/usr/bin/env python import zipfile import os import codecs import sys def recover(name): return codecs.decode(name, 'cp866') for filename in sys.argv[1:]: archive = zipfile.ZipFile(filename, 'r') infolist = archive.infolist() for i in infolist: f = recover(i.filename) print f if f.endswith("/"): os.makedirs(os.path.dirname(f)) else: open(f, 'w').write(archive.read(i)) archive.close()
UPD 2018-01-02 : Я использую chardet
пакет, чтобы угадать правильную кодировку необработанного фрагмента данных. Теперь скрипт работает "из коробки" на всех моих плохих архивах, а также на хороших.
Что следует отметить:
- Все имена файлов извлекаются и объединяются в одну строку, чтобы создать больший фрагмент текста для механизма кодирования. Это означает, что несколько имен файлов, закрученных по-разному, могут испортить предположение.
- Специальный быстрый путь был использован для обработки хорошего текста Unicode (
chardet
не работает с обычным объектом Unicode). - Тесты добавляются для тестирования и демонстрации того, что нормализатор распознает любую кодировку на достаточно коротких строках.
Окончательный вариант:
#!/usr/bin/env python2 # coding=utf-8 import zipfile import os import codecs import sys import chardet def make_encoding_normalizer(txt): u''' Takes raw data and returns function to normalize encoding of the data. * `txt` is either unicode or raw bytes; * `chardet` library is used to guess the correct encoding. >>> n_unicode = make_encoding_normalizer(u"Привет!") >>> print n_unicode(u"День добрый") День добрый >>> n_cp1251 = make_encoding_normalizer(u"Привет!".encode('cp1251')) >>> print n_cp1251(u"День добрый".encode('cp1251')) День добрый >>> type(n_cp1251(u"День добрый".encode('cp1251'))) <type 'unicode'> ''' if isinstance(txt, unicode): return lambda text: text enc = chardet.detect(txt)['encoding'] return lambda file_name: codecs.decode(file_name, enc) for filename in sys.argv[1:]: archive = zipfile.ZipFile(filename, 'r') infolist = archive.infolist() probe_txt = "\n".join(i.filename for i in infolist) normalizer = make_encoding_normalizer(probe_txt) for i in infolist: print i.filename f = normalizer(i.filename) print f dirname = os.path.dirname(f) if dirname: assert os.path.abspath(dirname).startswith(os.path.abspath(".")), \ "Security violation" if not os.path.exists(dirname): os.makedirs(dirname) if not f.endswith("/"): open(f, 'w').write(archive.read(i)) archive.close() if __name__ == '__main__' and len(sys.argv) == 1: # Hack for Python 2.x to support unicode source files as doctest sources. reload(sys) sys.setdefaultencoding("UTF-8") import doctest doctest.testmod() print "If there are no messages above, the script passes all tests."