Jak częściowo wyodrębnić spakowany ogromny zwykły plik tekstowy?

19

Mam plik zip o rozmiarze 1,5 GB.

Jego zawartość to jeden absurdalnie duży plik tekstowy (60 GB) i obecnie nie mam wystarczająco dużo miejsca na dysku, aby wyodrębnić to wszystko, ani nie chcę wyodrębnić tego wszystkiego, nawet gdybym miał.

Jeśli chodzi o mój przypadek użycia, wystarczyłbym, aby sprawdzić części treści.

Dlatego chcę rozpakować plik jako strumień i uzyskać dostęp do zakresu pliku (jak można to zrobić za pomocą głowy i ogona w normalnym pliku tekstowym).

Albo przez pamięć (np. Wyodrębnij maks. 100 kb, zaczynając od znaku 32 GB) lub przez linie (podaj mi zwykłe linie tekstowe 3700-3900).

Czy istnieje sposób na osiągnięcie tego?

k0pernikus
źródło
1
Niestety nie można szukać pojedynczego pliku w pliku zip. Więc każde rozwiązanie będzie polegało na przeczytaniu pliku do momentu, w którym jesteś zainteresowany.
plugwash
5
@plugwash Jak rozumiem pytanie, celem nie jest uniknięcie czytania pliku zip (lub nawet pliku zdekompresowanego), ale po prostu uniknięcie przechowywania całego zdekompresowanego pliku w pamięci lub na dysku. Zasadniczo traktuj zdekompresowany plik jako strumień .
ShreevatsaR

Odpowiedzi:

28

Zauważ, że gzipmożna wyodrębnić zippliki (przynajmniej pierwszy wpis w zippliku). Jeśli więc w tym archiwum jest tylko jeden ogromny plik, możesz:

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

Aby na przykład wyodrębnić 20 wierszy zaczynających się od 3000.

Lub:

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

To samo dotyczy bajtów (przy założeniu, headże obsługuje implementację -c).

Dla dowolnego dowolnego członka w archiwum, w sposób uniksowy:

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

Dzięki headwbudowanemu ksh93(np. Kiedy /opt/ast/binnadchodzi $PATH) możesz także:

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

Zauważ, że w każdym przypadku gzip/ bsdtar/ unzipzawsze będzie musiał rozpakować (i odrzucić tutaj) całą sekcję pliku, która prowadzi do części, którą chcesz wyodrębnić. To zależy od tego, jak działa algorytm kompresji.

Stéphane Chazelas
źródło
Jeśli gzipmożna go obsłużyć, będzie inne „Z aware” media ( zcat, zlessitp) również pracować?
ivanivan
@ivanivan, w systemach, na których są oparte gzip(generalnie prawdziwe zless, niekoniecznie z zcatktórych w niektórych systemach jest tylko do odczytu .Zplików), tak.
Stéphane Chazelas,
14

Jedno rozwiązanie wykorzystujące unzip -p i dd, na przykład, aby wyodrębnić 10kb z przesunięciem 1000 bloków:

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

Uwaga: nie próbowałem tego z naprawdę dużymi danymi ...

tonioc
źródło
W ogólnym przypadku więcej niż jednego pliku w jednym archiwum można użyć unzip -l ARCHIVEdo wyświetlenia zawartości archiwum i unzip -p ARCHIVE PATHwyodrębnienia zawartości pojedynczego obiektu PATHna standardowe wyjście.
David Foerster,
3
Zasadniczo używanie ddna potokach z liczeniem lub pomijaniem jest zawodne, ponieważ spowoduje to tak wiele read()s do 1024 bajtów. Gwarantuje to, że działa poprawnie tylko wtedy, gdy unzipzapisuje na rurze w częściach, których rozmiar jest wielokrotnością 1024.
Stéphane Chazelas
4

Jeśli masz kontrolę nad tworzeniem tego dużego pliku zip, dlaczego nie rozważyć zastosowania kombinacji gzipi zless?

Umożliwiłoby to korzystanie zlessz pagera i przeglądanie zawartości pliku bez konieczności zawracania sobie głowy rozpakowaniem.

Jeśli nie możesz zmienić formatu kompresji, to oczywiście nie zadziała. Jeśli tak, uważam, że zlessjest to raczej wygodne.

111 ---
źródło
1
Ja nie. Pobieram spakowany plik dostarczony przez firmę zewnętrzną.
k0pernikus
3

Aby wyświetlić określone wiersze pliku, potokuj dane wyjściowe do edytora strumieni Unix, sed . Może to przetwarzać dowolnie duże strumienie danych, dzięki czemu można nawet użyć ich do zmiany danych. Aby wyświetlić wiersze 3700-3900 zgodnie z zapytaniem, uruchom następujące polecenie.

unzip -p file.zip | sed -n 3700,3900p
Diomidis Spinellis
źródło
7
sed -n 3700,3900pbędzie czytać do końca pliku. Lepiej jest sed '3700,$!d;3900q'tego uniknąć, a nawet ogólnie bardziej wydajnie:tail -n +3700 | head -n 201
Stéphane Chazelas
3

Zastanawiałem się, czy można zrobić coś bardziej wydajnego niż dekompresję od początku pliku do samego momentu. Wygląda na to, że odpowiedź brzmi „nie”. Jednak na niektórych procesorach (Skylake) zcat | tailnie przyspiesza procesora do pełnej prędkości zegara. Patrz poniżej. Niestandardowy dekoder może uniknąć tego problemu i zapisać wywołania systemowe zapisu potoku, a może być o ~ 10% szybszy. (Lub ~ 60% szybciej w Skylake, jeśli nie zmienisz ustawień zarządzania energią).


Najlepsze, co możesz zrobić z dostosowanym zlibem z skipbytesfunkcją, to parsowanie symboli w bloku kompresji, aby dojść do końca bez konieczności rekonstrukcji zdekompresowanego bloku. Może to być znacznie szybsze (prawdopodobnie co najmniej 2x) niż wywołanie zwykłej funkcji dekodowania zlib w celu zastąpienia tego samego bufora i przejścia do przodu w pliku. Ale nie wiem, czy ktoś napisał taką funkcję. (I myślę, że to tak naprawdę nie działa, chyba że plik został napisany specjalnie, aby umożliwić dekoderowi ponowne uruchomienie w określonym bloku).

Miałem nadzieję, że istnieje sposób na przeskakiwanie bloków Deflate bez ich dekodowania, ponieważ byłoby to znacznie szybsze. Drzewo Huffmana jest wysyłane na początku każdego bloku, więc możesz dekodować od początku dowolnego bloku (tak myślę). Och, myślę, że stan dekodera jest czymś więcej niż drzewem Huffmana, jest to również poprzednie 32kB zdekodowanych danych i nie jest to domyślnie resetowane / zapominane ponad granicami bloków. Do tych samych bajtów można się ciągle odwoływać, więc mogą pojawić się dosłownie tylko raz w gigantycznym skompresowanym pliku. (np. w pliku dziennika nazwa hosta prawdopodobnie pozostaje „gorąca” w słowniku kompresji przez cały czas i każde jego wystąpienie odnosi się do poprzedniego, a nie pierwszego).

zlibInstrukcja mówi, trzeba użyć Z_FULL_FLUSHpodczas rozmowy deflate, jeśli chcesz, aby strumień sprężonego być możliwy do przeszukania do tego punktu. „Resetuje stan kompresji”, więc myślę, że bez tego referencje wstecz mogą przejść do poprzednich bloków. Tak więc, chyba że plik zip został napisany z okazjonalnymi pełnymi blokami (jak każdy 1G lub coś miałoby nieistotny wpływ na kompresję), myślę, że będziesz musiał wykonać więcej pracy dekodowania do pożądanego poziomu niż ja początkowo myślący. Myślę, że prawdopodobnie nie możesz zacząć od początku żadnego bloku.


Resztę tego napisałem, gdy myślałem, że można po prostu znaleźć początek bloku zawierającego pierwszy bajt, który chcesz, i stamtąd zdekodować.

Ale niestety początek bloku Deflate nie wskazuje, jak długo jest on w przypadku bloków skompresowanych. Dane nieskompresowane mogą być kodowane za pomocą nieskompresowanego typu bloku, który ma 16-bitowy rozmiar w bajtach z przodu, ale bloki skompresowane nie: RFC 1951 opisuje format dość czytelnie . Bloki z dynamicznym kodowaniem Huffmana mają drzewo z przodu bloku (więc dekompresor nie musi szukać w strumieniu), więc kompresor musi zachować cały (skompresowany) blok w pamięci przed jego zapisaniem.

Maksymalna odległość odniesienia do tyłu wynosi tylko 32 kB, więc kompresor nie musi przechowywać w pamięci dużej ilości nieskompresowanych danych, ale to nie ogranicza rozmiaru bloku. Bloki mogą mieć wiele megabajtów. (Jest to wystarczająco duży rozmiar, aby poszukiwania dysku były tego warte nawet na napędzie magnetycznym, w przeciwieństwie do sekwencyjnego odczytu do pamięci i po prostu pomijania danych w pamięci RAM, jeśli można było znaleźć koniec bieżącego bloku bez parsowania go).

zlib tworzy bloki tak długo, jak to możliwe: Według Marc Adler , zlib rozpoczyna nowy blok dopiero po zapełnieniu bufora symboli, który przy ustawieniu domyślnym to 16 383 symboli (literałów lub dopasowań)


Skopiowałem dane wyjściowe seq(który jest wyjątkowo redundantny i dlatego prawdopodobnie nie jest to świetny test), ale pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -cna tym działa tylko przy ~ 62 MiB / s skompresowanych danych na Skylake i7-6700k przy 3,9 GHz, z DDR4-2666 RAM. To 246 Mb / s zdekompresowanych danych, co jest zmianą w porównaniu do memcpyprędkości ~ 12 GiB / s dla bloków o zbyt dużych rozmiarach, aby zmieścić się w pamięci podręcznej.

(Przy energy_performance_preferenceustawieniu domyślnym balance_powerzamiast balance_performance, wewnętrzny regulator procesora Skylake decyduje się na działanie tylko przy 2,7 ​​GHz, ~ 43 MiB / s skompresowanych danych. Używam go, sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'aby go ulepszyć. Prawdopodobnie tak częste wywołania systemowe nie wyglądają jak prawdziwe związane z procesorem pracuj do jednostki zarządzania energią.)

TL: DR: zcat | tail -cjest związany z procesorem nawet na szybkim procesorze, chyba że masz bardzo wolne dyski. gzip wykorzystał 100% procesora, na którym działał (i uruchomił instrukcje 1,81 na zegar, zgodnie z perf) i tailwykorzystał 0,162 procesora, na którym działał (0,58 IPC). System był poza tym w większości bezczynny.

Używam Linuksa 4.14.11-1-ARCH, który ma domyślnie włączoną KPTI do pracy w Meltdown, więc wszystkie te writewywołania systemowe gzipsą droższe niż kiedyś: /


Posiadanie wbudowanego wyszukiwania do ( unziplub zcatnadal używanie zwykłej zlibfunkcji dekodowania) uratuje wszystkie zapisy potokowe i sprawi, że procesory Skylake będą działały z pełną prędkością zegara. (Ta redukcja w dół dla niektórych rodzajów obciążenia jest unikalna dla Intel Skylake i późniejszych, które odciążają proces podejmowania decyzji o częstotliwości procesora z systemu operacyjnego, ponieważ mają więcej danych na temat tego, co robi procesor, i mogą szybciej rosnąć / zwalniać. To jest normalnie dobre, ale tutaj prowadzi do tego, że Skylake nie przyspiesza do pełnej prędkości przy bardziej konserwatywnym ustawieniu gubernatora).

Żadne wywołania systemowe, po prostu przepisanie bufora pasującego do pamięci podręcznej L2, dopóki nie osiągniesz żądanej początkowej pozycji bajtu, prawdopodobnie spowodowałoby co najmniej kilka% różnic. Może nawet 10%, ale tutaj tylko tworzę liczby. Nie profilowałem zlibszczegółowo, aby zobaczyć, jak duży jest rozmiar pamięci podręcznej i jak bardzo opróżnianie TLB (a zatem opróżnianie pamięci podręcznej uop) przy każdym wywołaniu systemowym boli przy włączonym KPTI.


Istnieje kilka projektów oprogramowania, które dodają indeks wyszukiwania do formatu pliku gzip . Nie pomaga to, jeśli nie możesz wygenerować widocznych skompresowanych plików, ale inni przyszli czytelnicy mogą skorzystać.

Przypuszczalnie żaden z tych projektów nie ma funkcji dekodowania, która wie, jak przeskakiwać strumień Deflate bez indeksu, ponieważ są one zaprojektowane do działania tylko wtedy, gdy indeks jest dostępny.

Peter Cordes
źródło
1

Możesz otworzyć plik zip w sesji Pythona, używając zf = zipfile.ZipFile(filename, 'r', allowZip64=True)i po otwarciu możesz otworzyć, do odczytu, dowolny plik w archiwum zip i odczytywać wiersze itp., Tak jakby to był normalny plik.

Steve Barnes
źródło