26

В настоящее время tar -zcf arch.tgz files/* кодирует имена файлов в UTF, поэтому пользователи Windows видят все испорченные символы в именах файлов, которые не являются английскими, и ничего с этим не могут поделать.

zip -qq -r arch.zip files/* имеет такое же поведение.

Как мне создать архив zip / tgz, чтобы при извлечении пользователями Windows в нем были правильно закодированы все имена файлов?

6 ответов6

25

Вот простой скрипт Python, который я написал для распаковки tar-файлов из UNIX в Windows:

import tarfile

archive_name = "archive_name.tar"

def recover(name):
    return unicode(name, 'utf-8')

tar = tarfile.open(name=archive_name, 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()
24

В настоящее время tar кодирует имена файлов в UTF

На самом деле tar вообще не кодирует / не декодирует имена файлов, он просто копирует их из файловой системы как есть. Если ваша локаль основана на UTF-8 (как во многих современных дистрибутивах Linux), это будет UTF-8. К сожалению, системная кодовая страница окна Windows никогда не является UTF-8, поэтому имена всегда будут искажены, за исключением таких инструментов, как WinRAR, которые позволяют изменять кодировку.

Таким образом, невозможно создать ZIP-файл с именами, отличными от ASCII, которые работают в разных версиях Windows и поддерживают встроенные сжатые папки.

Недостатком форматов tar и zip является отсутствие фиксированной или предоставленной информации о кодировке, поэтому не-ASCII-символы всегда будут непереносимыми. Если вам нужен формат архива не ASCII, вам придется использовать один из более новых форматов, например, последние 7z или rar. К сожалению, они все еще шаткие; в 7zip вам нужен ключ -mcu , и rar по-прежнему не будет использовать UTF-8, если не обнаружит символы, отсутствующие в кодовой странице.

По сути, это ужасный беспорядок, и если вы сможете избежать распространения архивов, содержащих имена файлов с не-ASCII-символами, вам будет намного лучше.

8

Проблема с использованием в Linux tar по умолчанию (GNU tar) решена ... добавив параметр --format=posix при создании файла.

Например:
tar --format=posix -cf

В Windows для распаковки файлов я использую bsdtar.

В https://lists.gnu.org/archive/html/bug-tar/2005-02/msg00018.html написано (с 2005 года !!):

> Я прочитал что-то в ChangeLog о поддержке UTF-8. Что значит
> это значит?
> Я не нашел способа создать архив, который был бы взаимозаменяемым
> Между разными локалями.

При создании архивов в формате POSIX.1-2001 (tar --format = posix или --format = pax) tar преобразует имена файлов из текущей локали в UTF-8, а затем сохраняет их в архиве. При извлечении выполняется обратная операция.

PS Вместо ввода --format=posix вы можете ввести -H pax , что короче.

5

Я полагаю, что у вас возникли проблемы с самим форматом контейнера Zip. Тар может страдать от той же проблемы.

Вместо этого используйте архивные форматы 7zip (.7z) или RAR (.rar). Оба доступны для Windows и Linux; Программное обеспечение p7zip поддерживает оба формата.

Я только что протестировал создание .7z , .rar , .zip и .tar в WinXP и Debian 5, а файлы .7z и .rar правильно сохраняют / восстанавливают имена файлов, а файлы .zip и .tar - нет. Неважно, какая система используется для создания тестового архива.

5

У меня были проблемы с распаковкой файлов 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 , чтобы угадать правильную кодировку необработанного фрагмента данных. Теперь скрипт работает из коробки на всех моих плохих архивах, а также на хороших.

Что следует отметить:

  1. Все имена файлов извлекаются и объединяются в единую строку, чтобы сделать больший фрагмент текста для механизма кодирования. Это означает, что несколько имен файлов, закрученных по-разному, могут испортить предположение.
  2. Специальный быстрый путь был использован для обработки хорошего текста Unicode (chardet не работает с обычным объектом Unicode).
  3. Тесты добавляются для тестирования и демонстрации того, что нормализатор распознает любую кодировку на достаточно коротких строках.

Окончательный вариант:

#!/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."
4

В POSIX-1.2001 указано, как TAR использует UTF-8.

Начиная с 2007 года в версии журнала изменений 6.3.0 в приложении PKZIP APPNOTE.TXT (http://www.pkware.com/documents/casestudies/APPNOTE.TXT) указано, как ZIP использует UTF-8.

Только то, какие инструменты поддерживают эти стандарты должным образом, остается открытым вопросом.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .