Come estrarre parzialmente un enorme file di testo normale compresso?


19

Ho un file zip con dimensioni di 1,5 GB.

Il suo contenuto è un ridicolo file di solo testo di grandi dimensioni (60 GB) e al momento non ho abbastanza spazio sul mio disco per estrarlo tutto, né voglio estrarlo tutto, anche se avessi.

Per quanto riguarda il mio caso d'uso, basterebbe ispezionare parti del contenuto.

Quindi voglio decomprimere il file come flusso e accedere a un intervallo del file (come si può fare con la testa e la coda su un normale file di testo).

O per memoria (es. Estrarre max 100kb a partire da 32 GB) o per linee (datemi le righe di testo semplice 3700-3900).

C'è un modo per raggiungere questo obiettivo?


1
Purtroppo non è possibile cercare un singolo file all'interno di una zip. Quindi qualsiasi assunzione implica la lettura del file fino al punto che ti interessa.
Plugwash

5
@plugwash Come ho capito la domanda, l'obiettivo non è quello di evitare di leggere il file zip (o anche il file decompresso), ma semplicemente di evitare di memorizzare l'intero file decompresso in memoria o su disco. Fondamentalmente, trattare il file decompresso come un flusso .
ShreevatsaR,

Risposte:


28

Si noti che è gzippossibile estrarre i zipfile (almeno la prima voce nel zipfile). Quindi se c'è un solo file enorme in quell'archivio, puoi fare:

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

Ad esempio, per estrarre le 20 linee a partire da quella 3000.

O:

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

Per la stessa cosa con i byte (presupponendo headun'implementazione che supporti -c).

Per qualsiasi membro arbitrario nell'archivio, in modo Unixy:

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

Con il headbuilt-in ksh93(come quando /opt/ast/binè avanti $PATH), puoi anche fare:

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

Si noti che in ogni caso gzip/ bsdtar/ unzipsarà sempre necessario decomprimere (e scartare qui) l'intera sezione del file che porta alla porzione che si desidera estrarre. Questo dipende da come funziona l'algoritmo di compressione.


Se gzip grado di gestirlo, saranno le altre "z consapevoli" utilità ( zcat, zless, ecc) anche un lavoro?
Ivanivan,

@ivanivan, su sistemi su cui sono basati gzip(generalmente verozless , non necessariamente su zcatcui alcuni sistemi devono ancora leggere .Zsolo i file), sì.
Stéphane Chazelas,

14

Una soluzione che utilizza unzip -p e dd, ad esempio per estrarre 10kb con offset di 1000 blocchi:

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

Nota: non l'ho provato con dati davvero enormi ...


Nel caso generale di più file all'interno di un singolo archivio, è possibile utilizzare unzip -l ARCHIVEper elencare il contenuto dell'archivio ed unzip -p ARCHIVE PATHestrarre il contenuto di un singolo oggetto PATHsu stdout.
David Foerster

3
Generalmente, l'utilizzo ddsu pipe con count o skip è inaffidabile in quanto lo farà molte read()s fino a 1024 byte. Quindi è garantito che funzioni correttamente solo se unzipscrive sulla pipe in blocchi la cui dimensione è un multiplo di 1024.
Stéphane Chazelas

4

Se hai il controllo sulla creazione di quel file zip di grandi dimensioni, perché non considerare l'utilizzo di una combinazione di gzipe zless?

Ciò ti consentirebbe di utilizzare zlesscome cercapersone e visualizzare il contenuto del file senza doverti preoccupare dell'estrazione.

Se non è possibile modificare il formato di compressione, ciò ovviamente non funzionerebbe. Se è così, mi sento zlesspiuttosto conveniente.


1
Io non. Sto scaricando il file zippato fornito da una società esterna.
k0pernikus,

3

Per visualizzare linee specifiche del file, reindirizzare l'output all'editor di flusso Unix, sed . Questo può elaborare flussi di dati arbitrariamente grandi, quindi puoi persino usarli per modificare i dati. Per visualizzare le righe 3700-3900 come richiesto, eseguire quanto segue.

unzip -p file.zip | sed -n 3700,3900p

7
sed -n 3700,3900pcontinuerà a leggere fino alla fine del file. È meglio usarlo sed '3700,$!d;3900q'per evitarlo, o anche generalmente più efficiente:tail -n +3700 | head -n 201
Stéphane Chazelas

3

Mi chiedevo se fosse possibile fare qualcosa di più efficiente della decompressione dall'inizio del file fino al punto. Sembra che la risposta sia no. Tuttavia, su alcune CPU (Skylake)zcat | tail non aumenta la CPU alla massima velocità di clock. Vedi sotto. Un decodificatore personalizzato potrebbe evitare questo problema e salvare le chiamate di sistema di scrittura pipe e forse essere ~ 10% più veloce. (O ~ 60% più veloce su Skylake se non modifichi le impostazioni di risparmio energia).


Il meglio che potresti fare con uno zlib personalizzato con a skipbytes funzione sarebbe quello di analizzare i simboli in un blocco di compressione per arrivare alla fine senza fare il lavoro di ricostruzione effettiva del blocco decompresso. Questo potrebbe essere significativamente più veloce (probabilmente almeno 2x) rispetto alla chiamata della normale funzione di decodifica di zlib per sovrascrivere lo stesso buffer e andare avanti nel file. Ma non so se qualcuno abbia scritto una simile funzione. (E penso che questo in realtà non funzioni a meno che il file non sia stato scritto appositamente per consentire al decoder di riavviarsi ad un certo blocco).

Speravo che ci fosse un modo per saltare i blocchi Deflate senza decodificarli, perché sarebbe molto più veloce. L'albero di Huffman viene inviato all'inizio di ogni blocco, quindi puoi decodificare dall'inizio di qualsiasi blocco (credo). Oh, penso che lo stato del decodificatore sia più che l'albero di Huffman, sono anche i precedenti 32 kB di dati decodificati, e questo non è ripristinato / dimenticato attraverso i limiti del blocco per impostazione predefinita. Gli stessi byte possono continuare a essere referenziati ripetutamente, quindi potrebbero apparire letteralmente solo una volta in un file compresso gigante. (ad es. in un file di registro, il nome host probabilmente rimane "attivo" nel dizionario di compressione per tutto il tempo e ogni sua istanza fa riferimento al precedente, non al primo).

Il zlibmanuale dice che devi usare Z_FULL_FLUSHquando chiami deflatese vuoi che lo stream compresso sia ricercabile a quel punto. "Reimposta lo stato di compressione", quindi penso che senza di ciò, i riferimenti all'indietro possono andare nei blocchi precedenti. Quindi, a meno che il tuo file zip non sia stato scritto con blocchi full-flush occasionali (come ogni 1G o qualcosa avrebbe un impatto trascurabile sulla compressione), penso che dovresti fare più del lavoro di decodifica fino al punto desiderato di quanto non fossi inizialmente pensiero. Immagino che probabilmente non puoi iniziare all'inizio di nessun blocco.


Il resto è stato scritto mentre pensavo che sarebbe stato possibile trovare solo l'inizio del blocco contenente il primo byte desiderato e decodificare da lì.

Sfortunatamente, l'inizio di un blocco Deflate non indica quanto è lungo , per i blocchi compressi. I dati incomprimibili possono essere codificati con un tipo di blocco non compresso che ha una dimensione di 16 bit in byte nella parte anteriore, ma i blocchi compressi no: RFC 1951 descrive il formato in modo abbastanza leggibile . I blocchi con codifica Huffman dinamica hanno l'albero nella parte anteriore del blocco (quindi il decompressore non deve cercare nel flusso), quindi il compressore deve aver mantenuto l'intero blocco (compresso) in memoria prima di scriverlo.

La massima distanza di riferimento all'indietro è di soli 32 kB, quindi il compressore non deve conservare molti dati non compressi in memoria, ma ciò non limita la dimensione del blocco. I blocchi possono essere lunghi più megabyte. (Questo è abbastanza grande perché il disco cerchi di valerne la pena anche su un'unità magnetica, rispetto alla lettura sequenziale in memoria e semplicemente saltando i dati nella RAM, se fosse possibile trovare la fine del blocco corrente senza analizzarlo).

zlib crea blocchi il più a lungo possibile: secondo Marc Adler , zlib avvia un nuovo blocco solo quando il buffer dei simboli si riempie, che con l'impostazione predefinita è 16.383 simboli (letterali o corrispondenze)


Ho decompresso l'output di seq(che è estremamente ridondante e quindi probabilmente non è un ottimo test), ma pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -csu quello funziona solo a ~ 62 MiB / s di dati compressi su uno Skylake i7-6700k a 3,9 GHz, con RAM DDR4-2666. Sono 246 MiB / s di dati decompressi, che è una variazione di chump rispetto alla memcpyvelocità di ~ 12 GiB / s per blocchi di dimensioni troppo grandi per adattarsi alla cache.

(Con energy_performance_preferencel'impostazione predefinita balance_powerinvece di balance_performance, il regolatore della CPU interno di Skylake decide di funzionare solo a 2,7 GHz, ~ 43 MiB / s di dati compressi. Uso sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'per ottimizzarli. Probabilmente tali chiamate di sistema così frequenti non sembrano legate alla CPU reale lavorare all'unità di gestione dell'alimentazione.)

TL: DR: zcat | tail -cè associato alla CPU anche su una CPU veloce, a meno che tu non abbia dischi molto lenti. gzip ha usato il 100% della CPU su cui ha funzionato (e ha eseguito 1,81 istruzioni per clock, secondo perf), e ha tailusato 0,162 della CPU su cui ha funzionato (0,58 IPC). In caso contrario, il sistema era principalmente inattivo.

Sto usando Linux 4.14.11-1-ARCH, che ha KPTI abilitato di default a aggirare Meltdown, quindi tutte quelle writechiamate di sistema gzipsono più costose di quanto non fossero: /


Avere la ricerca integrata unzipo zcat(ma ancora usando la normale zlibfunzione di decodifica) salverebbe tutte quelle scritture di pipe e farebbe funzionare le CPU Skylake alla massima velocità di clock. (Questo downclocking per alcuni tipi di carico è univoco per Intel Skylake e versioni successive, che hanno scaricato il sistema decisionale sulla frequenza della CPU dal sistema operativo, poiché dispongono di più dati su ciò che la CPU sta facendo e possono aumentare / diminuire più rapidamente. normalmente buono, ma qui porta Skylake a non accelerare a tutta velocità con una regolazione più conservatrice del governatore).

Nessuna chiamata di sistema, solo riscrivere un buffer che si adatta alla cache L2 fino a raggiungere la posizione di byte iniziale desiderata, probabilmente farebbe almeno una differenza del qualche%. Forse anche il 10%, ma sto solo inventando numeri qui. Non ho profilato zlibin alcun dettaglio per vedere quanto è grande il footprint della cache e quanto il flush TLB (e quindi il flush della cache uop) su ogni chiamata di sistema fa male con KPTI abilitato.


Esistono alcuni progetti software che aggiungono un indice di ricerca al formato di file gzip . Questo non ti aiuta se non riesci a convincere nessuno a generare file compressi ricercabili per te, ma altri futuri lettori potrebbero trarne vantaggio.

Presumibilmente nessuno di questi progetti ha una funzione di decodifica che sa saltare un flusso Deflate senza un indice, perché sono progettati per funzionare solo quando un indice è disponibile.


1

Puoi aprire il file zip in una sessione Python, usando zf = zipfile.ZipFile(filename, 'r', allowZip64=True)e una volta aperto puoi aprire, per leggere, qualsiasi file all'interno dell'archivio zip e leggere righe, ecc., Da esso come se fosse un file normale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.