Comment extraire partiellement un énorme fichier texte brut zippé?

19

J'ai un fichier zip d'une taille de 1,5 Go.

Son contenu est un gros fichier de texte brut ridicule (60 Go) et je n'ai actuellement pas assez d'espace sur mon disque pour tout extraire et je ne veux pas tout extraire, même si je l'avais fait.

Quant à mon cas d'utilisation, il suffirait que je puisse inspecter des parties du contenu.

Par conséquent, je veux décompresser le fichier en tant que flux et accéder à une plage du fichier (comme on peut le faire via head et tail sur un fichier texte normal).

Soit par mémoire (par exemple extraire 100 Ko maximum à partir de 32 Go) ou par lignes (donnez-moi les lignes de texte brut 3700-3900).

Y a-t-il un moyen d'y parvenir?

k0pernikus
la source
1
Malheureusement, il n'est pas possible de rechercher un fichier individuel dans un zip. Donc, toute solution impliquera de lire le fichier jusqu'au point qui vous intéresse.
plugwash
5
@plugwash Si je comprends bien la question, le but n'est pas d'éviter de lire le fichier zip (ou même le fichier décompressé), mais simplement d'éviter de stocker l'intégralité du fichier décompressé en mémoire ou sur disque. Fondamentalement, traitez le fichier décompressé comme un flux .
ShreevatsaR

Réponses:

28

Notez que gzippeut extraire des zipfichiers (au moins la première entrée dans le zipfichier). Donc, s'il n'y a qu'un seul fichier énorme dans cette archive, vous pouvez faire:

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

Pour extraire les 20 lignes en commençant par la 3000e par exemple.

Ou:

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

Pour la même chose avec les octets (en supposant une headimplémentation qui prend en charge -c).

Pour tout membre arbitraire dans l'archive, de manière Unixy:

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

Avec la fonction headintégrée ksh93(comme quand /opt/ast/binest en avance $PATH), vous pouvez également faire:

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

Notez que dans tous les cas, gzip/ bsdtar/ unzipdevra toujours décompresser (et supprimer ici) la section entière du fichier qui mène à la partie que vous souhaitez extraire. Cela dépend de la façon dont l'algorithme de compression fonctionne.

Stéphane Chazelas
la source
Si gzippeut le manipuler, seront les autres services publics « au courant » z ( zcat, zless, etc.) aussi le travail?
ivanivan
@ivanivan, sur les systèmes sur lesquels ils sont basés gzip(généralement vrai zless, pas nécessairement zcatsur certains systèmes pour lire .Zuniquement les fichiers), oui.
Stéphane Chazelas
14

Une solution utilisant unzip -p et dd, par exemple pour extraire 10kb avec 1000 blocs offset:

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

Remarque: je n'ai pas essayé cela avec des données vraiment énormes ...

tonioc
la source
Dans le cas général de plusieurs fichiers dans une même archive, on peut utiliser unzip -l ARCHIVEpour répertorier le contenu de l'archive et unzip -p ARCHIVE PATHextraire le contenu d'un seul objet PATHvers stdout.
David Foerster
3
Généralement, l'utilisation ddsur des canaux avec count ou skip n'est pas fiable car elle fera autant de read()s jusqu'à 1024 octets. Il n'est donc garanti de fonctionner correctement que si unziple tube est écrit en morceaux dont la taille est un multiple de 1024.
Stéphane Chazelas
4

Si vous contrôlez la création de ce gros fichier zip, pourquoi ne pas envisager d'utiliser une combinaison de gzipet zless?

Cela vous permettrait d'utiliser zlesscomme pager et d'afficher le contenu du fichier sans avoir à vous soucier de l'extraction.

Si vous ne pouvez pas modifier le format de compression, cela ne fonctionnera évidemment pas. Si c'est le cas, je pense que zlessc'est plutôt pratique.

111 ---
la source
1
Je ne. Je télécharge le fichier compressé fourni par une entreprise externe.
k0pernikus
3

Pour afficher des lignes spécifiques du fichier, dirigez la sortie vers l'éditeur de flux Unix, sed . Cela peut traiter des flux de données arbitrairement volumineux, vous pouvez donc même les utiliser pour modifier les données. Pour afficher les lignes 3700-3900 comme vous l'avez demandé, exécutez ce qui suit.

unzip -p file.zip | sed -n 3700,3900p
Diomidis Spinellis
la source
7
sed -n 3700,3900pcontinuera à lire jusqu'à la fin du fichier. Il vaut mieux utiliser sed '3700,$!d;3900q'pour éviter cela, ou même généralement plus efficace:tail -n +3700 | head -n 201
Stéphane Chazelas
3

Je me demandais s'il était possible de faire quelque chose de plus efficace que la décompression du début du fichier jusqu'au point. Il semble que la réponse soit non. Cependant, sur certains processeurs (Skylake)zcat | tail ne fait pas monter le CPU à la vitesse d'horloge complète. Voir ci-dessous. Un décodeur personnalisé pourrait éviter ce problème et enregistrer les appels système d'écriture de tuyau, et peut-être être 10% plus rapide. (Ou ~ 60% plus rapide sur Skylake si vous ne modifiez pas les paramètres de gestion de l'alimentation).


Le mieux que vous puissiez faire avec un zlib personnalisé avec skipbytes fonction serait d'analyser les symboles dans un bloc de compression pour arriver à la fin sans faire le travail de reconstruction réelle du bloc décompressé. Cela pourrait être significativement plus rapide (probablement au moins 2x) que d'appeler la fonction de décodage régulière de zlib pour écraser le même tampon et avancer dans le fichier. Mais je ne sais pas si quelqu'un a écrit une telle fonction. (Et je pense que cela ne fonctionne réellement que si le fichier a été écrit spécialement pour permettre au décodeur de redémarrer à un certain bloc).

J'espérais qu'il y avait un moyen de sauter les blocs Deflate sans les décoder, car ce serait beaucoup plus rapide. L'arbre Huffman est envoyé au début de chaque bloc, vous pouvez donc décoder à partir du début de n'importe quel bloc (je pense). Oh, je pense que l'état du décodeur est plus que l'arbre de Huffman, c'est aussi les 32 ko de données décodées précédents, et ce n'est pas réinitialisé / oublié par-delà les limites des blocs par défaut. Les mêmes octets peuvent continuer à être référencés à plusieurs reprises, ils ne peuvent donc apparaître littéralement qu'une seule fois dans un fichier compressé géant. (Par exemple, dans un fichier journal, le nom d'hôte reste probablement "chaud" dans le dictionnaire de compression tout le temps, et chaque instance de celui-ci fait référence au précédent, pas au premier).

Le zlibmanuel indique que vous devez utiliser Z_FULL_FLUSHlors de l'appel deflatesi vous voulez que le flux compressé soit recherché à ce point. Il "réinitialise l'état de compression", donc je pense que sans cela, les références en arrière peuvent aller dans le (s) bloc (s) précédent (s). Donc, à moins que votre fichier zip n'ait été écrit avec des blocs de vidage occasionnels (comme chaque 1G ou quelque chose aurait un impact négligeable sur la compression), je pense que vous auriez à faire plus de travail de décodage jusqu'au point que vous vouliez qu'au départ. en pensant. Je suppose que vous ne pouvez probablement pas commencer au début d'un bloc.


Le reste de ceci a été écrit alors que je pensais qu'il serait possible de trouver juste le début du bloc contenant le premier octet que vous voulez, et de décoder à partir de là.

Mais malheureusement, le début d'un bloc Deflate n'indique pas sa durée , pour les blocs compressés. Les données incompressibles peuvent être codées avec un type de bloc non compressé qui a une taille de 16 bits en octets à l'avant, mais pas les blocs compressés: la RFC 1951 décrit le format de manière assez lisible . Les blocs avec codage Huffman dynamique ont l'arborescence à l'avant du bloc (donc le décompresseur n'a pas à chercher dans le flux), donc le compresseur doit avoir gardé le bloc entier (compressé) en mémoire avant de l'écrire.

La distance de référence arrière maximale n'est que de 32 Ko, de sorte que le compresseur n'a pas besoin de conserver beaucoup de données non compressées en mémoire, mais cela ne limite pas la taille du bloc. Les blocs peuvent avoir plusieurs mégaoctets. (Ceci est suffisamment grand pour que le disque en vaille la peine, même sur un lecteur magnétique, par rapport à la lecture séquentielle en mémoire et simplement à l'omission de données dans la RAM, s'il était possible de trouver la fin du bloc actuel sans l'analyse).

zlib crée des blocs le plus longtemps possible: selon Marc Adler , zlib ne démarre un nouveau bloc que lorsque le tampon de symboles se remplit, ce qui avec le paramètre par défaut est 16 383 symboles (littéraux ou correspondances)


J'ai compressé la sortie de seq(ce qui est extrêmement redondant et donc probablement pas un excellent test), mais pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -ccela ne fonctionne qu'à ~ 62 Mio / s de données compressées sur un Skylake i7-6700k à 3,9 GHz, avec DDR4-2666 RAM. Cela représente 246 Mo / s de données décompressées, ce qui représente un changement important par rapport à une memcpyvitesse de ~ 12 Gio / s pour des tailles de bloc trop grandes pour tenir dans le cache.

(Avec energy_performance_preferencela valeur par défaut balance_powerau lieu de balance_performance, le gouverneur de processeur interne de Skylake décide de ne fonctionner qu'à 2,7 GHz, ~ 43 Mio / s de données compressées. J'utilise sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'pour le modifier. Probablement, ces appels système fréquents ne ressemblent pas à de vrais CPU-liés travailler à l'unité de gestion de l'énergie.)

TL: DR: zcat | tail -cest lié au processeur même sur un processeur rapide, sauf si vous avez des disques très lents. gzip a utilisé 100% du processeur sur lequel il fonctionnait (et a exécuté 1,81 instructions par horloge, selon perf), et a tailutilisé 0,162 du processeur sur lequel il fonctionnait (0,58 IPC). Le système était par ailleurs essentiellement inactif.

J'utilise Linux 4.14.11-1-ARCH, qui a KPTI activé par défaut pour contourner Meltdown, donc tous ces writeappels système gzipsont plus chers qu'avant : /


Avoir la recherche intégrée à unzipou zcat(mais toujours en utilisant la zlibfonction de décodage régulière ) permettrait d'économiser toutes ces écritures de pipe et de faire fonctionner les processeurs Skylake à pleine vitesse d'horloge. (Ce downclocking pour certains types de charge est unique à Intel Skylake et versions ultérieures, qui déchargent la prise de décision de fréquence du processeur à partir du système d'exploitation, car ils ont plus de données sur ce que fait le processeur et peuvent augmenter / diminuer plus rapidement. normalement bon, mais ici, Skylake ne monte pas à pleine vitesse avec un réglage de gouverneur plus conservateur).

Aucun appel système, simplement réécrire un tampon qui tient dans le cache L2 jusqu'à ce que vous atteigniez la position d'octet de départ que vous souhaitez, ferait probablement au moins une différence de quelques%. Peut-être même 10%, mais je fais juste des chiffres ici. Je n'ai pas profilé zliben détail pour voir la taille d'une empreinte de cache, et combien le vidage TLB (et donc le vidage uop-cache) sur chaque appel système fait mal avec KPTI activé.


Il existe quelques projets logiciels qui ajoutent un index de recherche au format de fichier gzip . Cela ne vous aide pas si vous ne parvenez pas à faire en sorte que quelqu'un génère des fichiers compressés recherchés pour vous, mais d'autres futurs lecteurs pourraient en bénéficier.

Vraisemblablement, aucun de ces projets n'a de fonction de décodage qui sait ignorer un flux Deflate sans index, car ils ne sont conçus pour fonctionner que lorsqu'un index est disponible.

Peter Cordes
la source
1

Vous pouvez ouvrir le fichier zip dans une session python, en utilisant zf = zipfile.ZipFile(filename, 'r', allowZip64=True)et une fois ouvert, vous pouvez ouvrir, pour lire, n'importe quel fichier à l'intérieur de l'archive zip et lire des lignes, etc., comme s'il s'agissait d'un fichier normal.

Steve Barnes
la source