Miałem problemy z rozpakowywaniem tar
i zip
plikami otrzymywanymi od użytkowników systemu Windows. Chociaż nie odpowiadam na pytanie „jak utworzyć archiwum, które będzie działać”, poniższe skrypty pomagają poprawnie rozpakować pliki tar
i zip
niezależnie od oryginalnego systemu operacyjnego.
UWAGA: trzeba dostosować źródło kodujący ręcznie ( cp1251
, cp866
w przykładach poniżej). Opcje linii poleceń mogą być dobrym rozwiązaniem w przyszłości.
Smoła:
#!/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()
Zamek błyskawiczny:
#!/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 : Używam chardet
pakietu do odgadnięcia poprawnego kodowania surowej porcji danych. Teraz skrypt działa od razu na wszystkich moich złych archiwach, a także na dobrych.
Ważne uwagi:
- Wszystkie nazwy plików są wyodrębniane i łączone w pojedynczy ciąg, aby utworzyć większy fragment tekstu dla mechanizmu zgadywania kodowania. Oznacza to, że kilka nazw plików wkręconych w inny sposób może zepsuć domysły.
- Specjalna szybka ścieżka została wykorzystana do obsługi dobrego tekstu Unicode (
chardet
nie działa z normalnym obiektem Unicode).
- Testy dokumentów są dodawane w celu przetestowania i wykazania, że normalizator rozpoznaje kodowanie na stosunkowo krótkich ciągach.
Wersja ostateczna:
#!/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."