Распаковка файлов .gz и сохранение их в архиве .tar.gz - PullRequest
0 голосов
/ 02 января 2019

У меня следующая проблема: я пишу функцию, которая ищет кучу файлов .gz, распаковывает их и сохраняет отдельные несжатые файлы в большем архиве .tar.gz. До сих пор мне удалось реализовать его с помощью следующего кода, но вычисление размера несжатого файла вручную и установка размера TarInfo кажутся довольно хакерскими, и я хотел бы знать, есть ли более идиоматическое решение для моя проблема:

import gzip
import os
import pathlib
import tarfile

def gather_compressed_files(input_dir: pathlib.Path, output_file: str):
    with tarfile.open(output_file, 'w:gz') as tar:
        for input_file in input_dir.glob('*.gz'):
            with gzip.open(input_file) as fd:
                tar_info = tarfile.TarInfo(input_file.stem)
                tar_info.size = fd.seek(0, os.SEEK_END)
                fd.seek(0, os.SEEK_SET)
                tar.addfile(tar_info, fd)

Я попытался создать объект TarInfo следующим образом, вместо того, чтобы создавать его вручную:

tar_info = tar.gettarinfo(arcname=input_file.stem, fileobj=fd)

Однако эта функция извлекает путь к исходному файлу .gz, который мы открыли как fd, чтобы вычислить его размер, и, таким образом, предоставляет только параметр tar_info.size, соответствующий сжатым данным .gz, а не несжатому данные, которые не то, что я хочу. Не установка параметра tar_fino.size вообще не работает, потому что addfile использует указанный размер при передаче дескриптора файла.

Есть ли лучший идиоматичный способ добиться этого или я застрял в своем текущем решении?

1 Ответ

0 голосов
/ 02 января 2019

Ваш подход - единственный способ избежать полного распаковки файла на диск или в ОЗУ. В конце концов, вам нужно знать размер заранее, чтобы добавить его в tar-файл, и gzip файлы не знают своего собственного распакованного размера. Поле заголовка ISIZE теоретически предоставляет распакованный размер, но поле было определено еще в 32-битных днях, так что на самом деле это размер по модулю 2**32; файл, изначально размером 4 ГБ и файл размером 0 B, будут иметь такой же ISIZE. В любом случае, Python не предоставляет ISIZE, поэтому, даже если бы это было полезно, не было бы встроенного способа сделать это (вы всегда можете обойтись ручным анализом, но это не совсем чисто или идиоматично).

Если вы хотите избежать распаковки файла дважды (один раз до seek вперед, один раз, чтобы фактически добавить его в файл tar), за счет распаковки его на диск, вы можете использовать tempfile.TemporaryFile, чтобы избежать двойного распаковка (без необходимости сохранения исходного файла в памяти) с небольшим изменением:

import shutil
import tempfile

def gather_compressed_files(input_dir: pathlib.Path, output_file: str):
    with tarfile.open(output_file, 'w:gz') as tar:
        for input_file in input_dir.glob('*.gz'):
            with tempfile.TemporaryFile() as tf:
                # Could combine both in one with, but this way we close the gzip
                # file ASAP
                with gzip.open(input_file) as fd:
                    shutil.copyfileobj(fd, tf)
                tar_info = tarfile.TarInfo(input_file.stem)
                tar_info.size = tf.tell()
                tf.seek(0)
                tar.addfile(tar_info, tf)
...