Почему .tar.gz нельзя склеить через cat
Алекс Чан столкнулся с обыденной задачей: его проект создаёт несколько архивов .tar.gz, и их нужно свести в один файл. Казалось бы, достаточно склеить байты командой cat — но результат читаться не желает. Разбор этой ошибки заставил автора пересмотреть собственное представление о том, как устроены tar и gzip, и завёл его в дебри ленточных накопителей и патентного права.
tar помнит про ленты
Название tar расшифровывается как tape archive, и структура формата напрямую отражает ограничения магнитных лент: последовательное чтение, дозапись только в конец, фиксированный размер блоков. Внутри tar — это череда файлов, каждый со своим заголовком (имя, размер, время) и блоками данных. Завершают всё два или более блока, заполненных нулями: это маркер конца файла (EOF), который велит читателю игнорировать всё, что идёт дальше.
Эта же логика объясняет несколько неочевидных особенностей формата. Размер файла приходится указывать в заголовке заранее — забудешь про tarinfo.size в Python, и получишь пустой архив. Внутри могут быть файлы с одинаковыми именами: раз стереть блок на ленте нельзя, обновление дописывается в конец, а при распаковке поздняя версия перекрывает раннюю. И, наконец, всё после EOF-маркера просто отбрасывается.
Именно поэтому наивная склейка через cat проваливается: программа-читатель доходит до первого EOF-маркера первого архива и останавливается.
gzip помнит про патенты
gzip — потоковый компрессор, сжимающий один поток данных без потерь. Его форму определили не ленты, а обстоятельства эпохи: он задумывался как свободная замена утилиты compress, чей алгоритм LZW был обременён патентами. Отсюда требования — работать без патентов, сжимать и разжимать в небольшом фиксированном объёме памяти (RAM в начале 1990-х была дефицитом) и быть переносимым между системами.
Внутри gzip-файл — это последовательность «членов», каждый со своим заголовком, сжатыми данными и трейлером с контрольной суммой. Маркера конца у gzip нет. Соблазнительно принять члены за аналог файлов, но это не так: инструменты считают их частями единого потока, извлечь по отдельности их нельзя, а при распаковке получается один цельный поток. Раз EOF-маркера нет, gzip-файлы прекрасно склеиваются простым cat.
Что происходит с .tar.gz
Тут-то и кроется подвох. Когда склеиваешь .tar.gz, gzip послушно сливает свои члены в единый поток — но распакованный поток снова читает tar, натыкается на EOF-маркер первого архива и останавливается. Никакого волшебного сокращения нет.
Правильный путь — распаковать члены и записать их в новый архив с единственным EOF-маркером. Автор приводит короткую функцию на Python с модулем tarfile, где режимы чтения и записи меняются на r:gz/w:gz. Кода получается больше, чем при склейке байтов, зато итоговый архив читается стандартными средствами, без флагов вроде --ignore-zeros.
История, начавшаяся с досадной ошибки, обернулась приятным побочным квестом: теперь автор точно знает, что не упустил никакой хитрой оптимизации.
Источник: Lobsters
Комментарии
Войдите, чтобы комментировать.