Как частично распаковать архив в виде простого простого текстового файла?

19

У меня есть zip-файл размером 1,5 ГБ.

Его содержимое представляет собой один смешной большой текстовый файл (60 ГБ), и в настоящее время у меня недостаточно свободного места на диске, чтобы извлечь все, и при этом я не хочу извлекать все это, даже если бы у меня было.

Что касается моего варианта использования, было бы достаточно, если бы я мог проверять части содержимого.

Следовательно, я хочу разархивировать файл как поток и получить доступ к диапазону файла (как можно через голову и хвост в обычном текстовом файле).

Либо по памяти (например, извлечение максимум 100 КБ, начиная с отметки 32 ГБ), либо по строкам (дайте мне строки обычного текста 3700-3900).

Есть ли способ достичь этого?

k0pernikus
источник
1
К сожалению, поиск по отдельному файлу внутри почтового индекса невозможен. Таким образом, любое решение будет включать в себя чтение файла до интересующего вас момента.
plugwash
5
@plugwash Как я понимаю, цель состоит не в том, чтобы избежать чтения zip-файла (или даже распакованного файла), а просто в том, чтобы не хранить весь распакованный файл в памяти или на диске. По сути, обрабатывайте распакованный файл как поток .
ShreevatsaR

Ответы:

28

Обратите внимание, что gzipможет извлекать zipфайлы (по крайней мере, первая запись в zipфайле). Так что если в этом архиве только один огромный файл, вы можете сделать:

gunzip < file.zip | tail -n +3000 | head -n 20

Например, извлечь 20 строк, начиная с 3000-й.

Или:

gunzip < file.zip | tail -c +3000 | head -c 20

Для того же самого с байтами (предполагая headреализацию, которая поддерживает -c).

Для любого произвольного члена в архиве Unixy-способом:

bsdtar xOf file.zip file-to-extract | tail... | head...

С помощью headвстроенной функции ksh93(например, когда /opt/ast/binвпереди $PATH) вы также можете сделать:

.... | head     -s 2999      -c 20
.... | head --skip=2999 --bytes=20

Обратите внимание, что в любом случае gzip/ bsdtar/ unzipвсегда нужно будет распаковывать (и отбрасывать здесь) весь раздел файла, который ведет к той части, которую вы хотите извлечь. Это зависит от того, как работает алгоритм сжатия.

Стефан Шазелас
источник
Если gzipсправится, будут ли работать другие утилиты zcat, zlessработающие с z, ( и т. Д.)?
Иваниван
@ivanivan, в системах, на которых они основаны gzip(как правило, это правда zless, не обязательно из zcatкоторых в некоторых системах все еще только для чтения .Zфайлов), да.
Стефан Шазелас
14

Одно решение, использующее unzip -p и dd, например, для извлечения 10 КБ со смещением 1000 блоков:

$ unzip -p my.zip | dd ibs=1024 count=10 skip=1000 > /tmp/out

Примечание: я не пробовал это с действительно большими данными ...

tonioc
источник
В общем случае более одного файла внутри одного архива можно использовать unzip -l ARCHIVEдля просмотра содержимого архива и unzip -p ARCHIVE PATHизвлечения содержимого одного объекта PATHв стандартный вывод.
Дэвид Фёрстер
3
Как правило, использование ddна трубах с графом или пропустить ненадежно , как он будет делать , что многие read()х до до 1024 байт. Таким образом, он гарантированно будет работать правильно только при unzipзаписи в канал кусками, размер которых кратен 1024.
Стефан Шазелас
4

Если у вас есть контроль над созданием этого большого zip-файла, почему бы не рассмотреть возможность использования комбинации gzipи zless?

Это позволит вам использовать его zlessкак пейджер и просматривать содержимое файла без необходимости извлечения.

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

111 ---
источник
1
Я не. Я загружаю заархивированный файл, предоставленный сторонней компанией.
k0pernikus
3

Чтобы просмотреть конкретные строки файла, передайте вывод в потоковый редактор Unix sed . Это может обрабатывать произвольно большие потоки данных, так что вы даже можете использовать их для изменения данных. Чтобы просмотреть строки 3700-3900, как вы просили, выполните следующее.

unzip -p file.zip | sed -n 3700,3900p
Диомидис Спинеллис
источник
7
sed -n 3700,3900pпродолжит чтение до конца файла. Лучше использовать, sed '3700,$!d;3900q'чтобы избежать этого, или даже вообще более эффективно:tail -n +3700 | head -n 201
Стефан Шазелас
3

Я задавался вопросом, можно ли сделать что-нибудь более эффективное, чем распаковка от начала файла до момента. Похоже, что ответ - нет. Однако на некоторых процессорах (Skylake) процессор zcat | tailне разгоняется до полной тактовой частоты. См. ниже. Пользовательский декодер может избежать этой проблемы и сохранить системные вызовы записи канала, и может быть на ~ 10% быстрее. (Или ~ 60% быстрее на Skylake, если вы не настраиваете параметры управления питанием).


Лучшее, что вы могли бы сделать с настраиваемым zlib с skipbytesфункцией - это проанализировать символы в блоке сжатия, чтобы добраться до конца, не выполняя работу по фактической реконструкции распакованного блока. Это может быть значительно быстрее (возможно, по крайней мере, в 2 раза), чем вызов обычной функции декодирования zlib для перезаписи того же буфера и перемещения вперед в файле. Но я не знаю, написал ли кто-нибудь такую ​​функцию. (И я думаю, что это на самом деле не работает, если файл не был написан специально, чтобы позволить декодеру перезапуститься с определенного блока)

Я надеялся, что есть способ пропустить блоки Deflate без их декодирования, потому что это будет намного быстрее. Дерево Хаффмана отправляется в начале каждого блока, так что вы можете декодировать с начала любого блока (я думаю). О, я думаю, что состояние декодера больше, чем дерево Хаффмана, это также предыдущие 32 КБ декодированных данных, и это не сбрасывается / не забывается через границы блоков по умолчанию. На одни и те же байты можно постоянно ссылаться, поэтому они могут появляться буквально один раз в гигантском сжатом файле. (например, в файле журнала имя хоста, вероятно, остается «горячим» в словаре сжатия все время, и каждый его экземпляр ссылается на предыдущий, а не на первый).

В zlibруководстве говорится, что вы должны использовать Z_FULL_FLUSHпри вызове, deflateесли вы хотите, чтобы сжатый поток был доступен для поиска до этого момента. Он «сбрасывает состояние сжатия», поэтому я думаю, что без этого обратные ссылки могут перейти в предыдущий блок (ы). Таким образом, если ваш zip-файл не был написан со случайными блоками полного сброса (например, каждый 1G или что-то еще не оказало бы незначительного влияния на сжатие), я думаю, вам придется выполнять большую часть работы по декодированию до того уровня, который вы хотите, чем я был изначально мышление. Я думаю, что вы, вероятно, не можете начать в начале любого блока.


Остальное было написано, пока я думал, что можно будет просто найти начало блока, содержащего первый нужный байт, и декодировать оттуда.

Но, к сожалению, начало блока Deflate не указывает, как долго это будет , для сжатых блоков. Несжимаемые данные могут быть закодированы с несжатым типом блока, который имеет 16-битный размер в байтах на передней панели, но сжатые блоки этого не делают: RFC 1951 описывает формат довольно читабельно . Блоки с динамическим кодированием Хаффмана имеют дерево в передней части блока (поэтому декомпрессор не должен искать в потоке), поэтому перед записью компрессор должен сохранить весь (сжатый) блок в памяти.

Максимальное расстояние обратного отсчета составляет всего 32 кБ, поэтому компрессору не нужно хранить много несжатых данных в памяти, но это не ограничивает размер блока. Блоки могут быть длиной в несколько мегабайт. (Это достаточно для того, чтобы поиск диска стоил того даже на магнитном диске, по сравнению с последовательным считыванием в память и просто пропуском данных в ОЗУ, если можно было найти конец текущего блока, не анализируя его).

zlib создает блоки как можно дольше: согласно Марку Адлеру , zlib начинает новый блок только тогда, когда заполнен буфер символов, который по умолчанию равен 16 383 символам (литералам или совпадениям)


Я распаковал вывод seq(который чрезвычайно избыточен и, следовательно, вероятно, не очень хороший тест), но pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -cна этом он работает со скоростью ~ 62 МБ / с сжатых данных на Skylake i7-6700k с частотой 3,9 ГГц и оперативной памятью DDR4-2666. Это 246 МБ / с декомпрессированных данных, что является частичным изменением по сравнению со memcpyскоростью ~ 12 ГБ / с для блоков слишком большого размера, чтобы поместиться в кэш.

energy_performance_preferenceнабором по умолчанию balance_powerвместо balance_performanceгубернатор внутреннего процессора Skylake решает только работать на 2.7GHz, ~ 43 MiB / с сжатыми данными. Я использую , sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'чтобы настроить его. Возможно , такие частые системные вызовы не выглядят как настоящие CPU переплет работать с блоком управления питанием.)

TL: DR: zcat | tail -cпривязан к процессору даже на быстром процессоре, если только у вас не очень медленные диски. gzip использовал 100% процессора, на котором он работал (и выполнял 1,81 инструкции в такт, согласно perf), и tailиспользовал 0,162 процессора, на котором он работал (0,58 IPC). В остальном система в основном простаивала.

Я использую Linux 4.14.11-1-ARCH, в которой по умолчанию включен KPTI для обхода Meltdown, поэтому все эти writeсистемные вызовы gzipобходятся дороже, чем раньше: /


Наличие встроенного поиска для unzipили zcat(но все еще использующего обычную zlibфункцию декодирования) сохранит все эти записи канала и заставит процессоры Skylake работать на полной тактовой частоте. (Этот разгон для некоторых видов нагрузки является уникальным для Intel Skylake и более поздних версий, которые переносят процесс принятия решений о частоте ЦП из ОС, потому что у них больше данных о том, что делает ЦП, и они могут увеличивать / уменьшать скорость быстрее. как правило, хорошо, но здесь приводит к тому, что Skylake не разгоняется до полной скорости при более консервативной настройке регулятора).

Никакие системные вызовы, просто переписывание буфера, который помещается в кэш L2 до тех пор, пока вы не достигнете желаемой позиции начального байта, вероятно, будет иметь разницу как минимум на несколько%. Может быть, даже 10%, но я просто придумываю цифры здесь. Я не стал zlibподробно рассказывать, насколько велика его площадь кэш-памяти и сколько сбрасывает TLB (и, следовательно, очищает uop-cache) при каждом системном вызове при включенном KPTI.


Есть несколько программных проектов, которые добавляют индекс поиска в формат файла gzip . Это не поможет вам, если вы не сможете заставить кого-либо создавать для вас сжатые файлы, которые могут быть найдены для поиска, но другие будущие читатели могут выиграть.

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

  • GZinga: поиск и разделение Gzip . Позволяет большие размеры блока.
  • BGZF - заблокированный, большой и лучший GZIP! (малый максимальный размер блока = 64 КБ немного ухудшает степень сжатия. Предназначен для использования с данными биоинформатики, такими как FASTA, которые часто используются без сжатия, с прозрачной поддержкой в ​​некоторых библиотеках Python.)
Питер Кордес
источник
1

Вы можете открыть zip-файл в сеансе Python, используя zf = zipfile.ZipFile(filename, 'r', allowZip64=True)и, открыв его, вы можете открыть для чтения любой файл в zip-архиве, прочитать строки и т. Д. Из него, как если бы это был обычный файл.

Стив Барнс
источник