Як частково витягнути величезний звичайний текстовий файл?


19

У мене zip-файл розміром 1,5 ГБ.

Його вміст - це один смішний великий звичайний текстовий файл (60 ГБ), і в даний час у мене на диску не вистачає місця для його витягування, і я не хочу все це витягнути, навіть якби у мене був.

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

Отже, я хочу розпакувати файл як потік та отримати доступ до діапазону файлів (як, наприклад, один за допомогою головки та хвоста у звичайному текстовому файлі).

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

Чи є спосіб досягти цього?


1
На жаль, неможливо шукати окремий файл у межах zip. Таким чином, будь-яке рішення може включати читання файлу до моменту, який вас цікавить.
Підключіть

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може впоратися з цим, буде інший «Z» обізнані комунальні послуги ( zcat, zless, і т.д.) також роботу?
іваніван

@ivanivan, на системах, на яких вони базуються gzip(як правило, правда zless, zcatщо в деяких системах все-таки читати .Zлише файли), так.
Стефан Шазелас

14

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

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

Примітка. Я не пробував цього з дуже величезними даними ...


У загальному випадку більше одного разу файл всередині одного архіву можна використовувати unzip -l ARCHIVEдля переліку вмісту архіву та unzip -p ARCHIVE PATHвилучення вмісту одного об’єкта PATHдо stdout.
Девід Фоерстер

3
Як правило, використання ddна трубах з підрахунком або пропуском є ​​ненадійним, оскільки це зробить багато read()с до 1024 байт. Тож гарантовано працювати належним чином лише у тому випадку, якщо unzipзапише в трубу шматками, розмір яких кратний 1024.
Стефан Шазелас

4

Якщо у вас є контроль над створенням цього великого zip-файлу, чому б не розглянути можливість використання комбінації gzipта zless?

Це дозволить вам використовувати zlessяк пейджер і переглядати вміст файлу, не заважаючи видобутку.

Якщо ви не можете змінити формат стиснення, це, очевидно, не спрацює. Якщо так, то мені здається, що zlessце досить зручно.


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 або щось щось мало б незначний вплив на стиснення), я думаю, вам доведеться виконати більше роботи над розшифровкою до потрібної точки, ніж я був спочатку мислення. Я думаю, ви, мабуть, не можете почати будь-який блок.


Решта цього написана, поки я думав, що можна буде просто знайти початок блоку, що містить перший байт, який ви хочете, і розшифрувати звідти.

Але, на жаль, запуск блоку відхилення не вказує на тривалість стиснених блоків. Нестислимі дані можуть бути кодовані типом блоку без стиснення, який має 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,7 ГГц, ~ 43 Мбіт / с стислих даних. Я використовую його sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'для налаштування. Можливо, такі часті системні дзвінки не схожі на реальні пов'язані з процесором робота з енергоуправлінням.)

TL: DR: zcat | tail -cCPU пов'язаний навіть у швидкому процесорі, якщо у вас дуже повільні диски. 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 (і, таким чином, загально-кеш-флеш) при кожному системному виклику болить при включеному KPTI.


Існує декілька програм, які додають індекс пошуку у формат файлу gzip . Це не допоможе вам, якщо ви не можете змусити когось генерувати для вас стиснуті файли, але інші майбутні читачі можуть отримати користь.

Імовірно ні один з цих проектів не має функції декодування , яка знає , як пропустити через потік Deflate без індексу, тому що вони призначені тільки для роботи , коли індекс є доступний.


1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.