Como extrair parcialmente um grande arquivo de texto sem formatação compactado?

19

Eu tenho um arquivo zip com tamanho de 1,5 GB.

Seu conteúdo é um arquivo de texto simples grande e ridículo (60 GB) e, atualmente, não tenho espaço suficiente no disco para extrair tudo, nem quero extrair tudo, mesmo que tivesse.

Quanto ao meu caso de uso, seria suficiente se eu pudesse inspecionar partes do conteúdo.

Por isso, quero descompactar o arquivo como um fluxo e acessar um intervalo do arquivo (como se pode via cabeça e cauda em um arquivo de texto normal).

Pela memória (por exemplo, extraia no máximo 100kb a partir da marca de 32GB) ou por linhas (dê-me as linhas de texto sem formatação 3700-3900).

Existe uma maneira de conseguir isso?

k0pernikus
fonte
1
Infelizmente, não é possível procurar em um arquivo individual dentro de um zip. Assim, qualquer soloution envolverá a leitura através do arquivo até o ponto que você está interessado.
plugwash
5
@ plugwash Pelo que entendi, o objetivo não é evitar a leitura do arquivo zip (ou mesmo o arquivo descompactado), mas simplesmente evitar o armazenamento de todo o arquivo descomprimido na memória ou no disco. Basicamente, trate o arquivo descompactado como um fluxo .
ShreevatsaR 10/01

Respostas:

28

Observe que gzippode extrair ziparquivos (pelo menos a primeira entrada no ziparquivo). Portanto, se houver apenas um arquivo enorme nesse arquivo, você poderá:

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

Para extrair as 20 linhas começando pela 3000, por exemplo.

Ou:

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

Para a mesma coisa com bytes (assumindo uma headimplementação que suporta -c).

Para qualquer membro arbitrário do arquivo, de maneira Unixy:

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

Com o headbuilt-in de ksh93(como quando /opt/ast/binestá à frente $PATH), você também pode:

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

Observe que, em qualquer caso, gzip/ bsdtar/ unzipsempre precisará descompactar (e descartar aqui) toda a seção do arquivo que leva à parte que você deseja extrair. Isso é como o algoritmo de compactação funciona.

Stéphane Chazelas
fonte
Se gzippode lidar com isso, será que as outras "z conscientes" utilities ( zcat, zlessetc.) também trabalho?
ivanivan
@ivanivan, em sistemas nos quais eles se baseiam gzip(geralmente verdade zless, não necessariamente do zcatque em alguns sistemas ainda é .Zapenas para ler arquivos), sim.
Stéphane Chazelas
14

Uma solução usando descompacte -p e dd, por exemplo, para extrair 10kb com deslocamento de 1000 blocos:

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

Nota: não tentei isso com dados realmente enormes ...

tonioc
fonte
No caso geral de mais de uma vez, o arquivo dentro de um único archive pode ser usado unzip -l ARCHIVEpara listar o conteúdo do archive e unzip -p ARCHIVE PATHextrair o conteúdo de um único objeto PATHpara o stdout.
David Foerster
3
Geralmente, o uso ddem pipes com count ou skip não é confiável, pois fará muitos read()s de até 1024 bytes. Por isso, só é garantido para trabalho corretamente se unzipescreve para o tubo em pedaços, cujo tamanho é um múltiplo de 1024.
Stéphane Chazelas
4

Se você tem controle sobre a criação desse grande arquivo zip, por que não considerar usar uma combinação de gzipe zless?

Isso permitiria que você usasse zlesscomo pager e visualizasse o conteúdo do arquivo sem precisar se preocupar com a extração.

Se você não pode alterar o formato de compactação, isso obviamente não funcionaria. Se assim for, eu sinto que zlessé bastante conveniente.

111 ---
fonte
1
Eu não. Estou baixando o arquivo compactado fornecido por uma empresa externa.
precisa saber é o seguinte
3

Para visualizar linhas específicas do arquivo, canalize a saída para o editor de fluxo Unix, sed . Isso pode processar fluxos de dados arbitrariamente grandes, para que você possa usá-lo para alterar os dados. Para visualizar as linhas 3700-3900 conforme solicitado, execute o seguinte.

unzip -p file.zip | sed -n 3700,3900p
Diomidis Spinellis
fonte
7
sed -n 3700,3900pcontinuará lendo até o final do arquivo. É melhor usar sed '3700,$!d;3900q'para evitar isso, ou mesmo geralmente mais eficiente:tail -n +3700 | head -n 201
Stéphane Chazelas
3

Gostaria de saber se era possível fazer algo mais eficiente do que descomprimir desde o início do arquivo até o ponto. Parece que a resposta é não. No entanto, em algumas CPUs (Skylake) zcat | tailnão aumenta a CPU até a velocidade máxima do relógio. Ver abaixo. Um decodificador personalizado pode evitar esse problema e salvar as chamadas do sistema de gravação de canal e talvez ser ~ 10% mais rápido. (Ou ~ 60% mais rápido no Skylake, se você não ajustar as configurações de gerenciamento de energia).


O melhor que você poderia fazer com um zlib personalizado com uma skipbytesfunção seria analisar os símbolos em um bloco de compactação para chegar ao fim sem fazer o trabalho de reconstruir o bloco descompactado. Isso pode ser significativamente mais rápido (provavelmente pelo menos 2x) do que chamar a função de decodificação regular do zlib para substituir o mesmo buffer e avançar no arquivo. Mas não sei se alguém escreveu essa função. (E acho que isso realmente não funciona, a menos que o arquivo tenha sido escrito especialmente para permitir que o decodificador reinicie em um determinado bloco).

Eu esperava que houvesse uma maneira de pular os blocos Deflate sem decodificá-los, porque isso seria muito mais rápido. A árvore Huffman é enviada no início de cada bloco, para que você possa decodificar desde o início de qualquer bloco (eu acho). Ah, acho que o estado do decodificador é mais do que a árvore de Huffman, também são os 32 kiB anteriores de dados decodificados, e isso não é redefinido / esquecido nos limites do bloco por padrão. Os mesmos bytes podem continuar sendo referenciados repetidamente, portanto, podem aparecer literalmente apenas uma vez em um arquivo compactado gigante. (por exemplo, em um arquivo de log, o nome do host provavelmente permanece "quente" no dicionário de compactação o tempo todo, e todas as instâncias referenciam o anterior, não o primeiro).

O zlibmanual diz que você deve usar Z_FULL_FLUSHao ligar deflatese quiser que o fluxo compactado seja procurado até esse ponto. Ele "redefine o estado de compactação", então acho que sem isso, as referências anteriores podem ir para o (s) bloco (s) anterior (es). Portanto, a menos que seu arquivo zip tenha sido gravado com blocos ocasionais de liberação completa (como todo 1G ou algo teria um impacto insignificante na compactação), acho que você teria que fazer mais do trabalho de decodificação até o ponto desejado do que eu estava inicialmente pensando. Eu acho que você provavelmente não pode começar no início de qualquer bloco.


O resto disso foi escrito enquanto eu pensava que seria possível encontrar o início do bloco que contém o primeiro byte desejado e decodificar a partir daí.

Infelizmente, porém, o início de um bloco Deflate não indica quanto tempo é , para blocos compactados. Os dados incompatíveis podem ser codificados com um tipo de bloco não compactado que possui um tamanho de 16 bits em bytes na frente, mas os blocos compactados não: O RFC 1951 descreve o formato com bastante facilidade . Blocos com codificação dinâmica de Huffman têm a árvore na frente do bloco (para que o descompressor não precise procurar no fluxo), portanto o compressor deve manter todo o bloco (comprimido) na memória antes de escrevê-lo.

A distância máxima de referência para trás é de apenas 32 kB, portanto o compressor não precisa manter muitos dados não compactados na memória, mas isso não limita o tamanho do bloco. Os blocos podem ter vários megabytes. (Isso é grande o suficiente para que o disco busque valer a pena, mesmo em uma unidade magnética, vs. leitura seqüencial na memória e apenas pular dados na RAM, se for possível encontrar o final do bloco atual sem analisá-lo).

O zlib cria blocos o maior tempo possível: De acordo com Marc Adler , o zlib só inicia um novo bloco quando o buffer do símbolo é preenchido, que com a configuração padrão é 16.383 símbolos (literais ou correspondências)


Gzipei a saída de seq(que é extremamente redundante e, portanto, provavelmente não é um ótimo teste), mas pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -cé executado com apenas 62 MiB / s de dados compactados em um Skylake i7-6700k a 3.9GHz, com DDR4-2666 RAM. São 246MiB / s de dados descomprimidos, que são trocas de chump em comparação com a memcpyvelocidade de ~ 12 GiB / s para tamanhos de bloco grandes demais para caber no cache.

(Com energy_performance_preferenceo padrão definido em balance_powervez de balance_performance, o governador interno da CPU do Skylake decide rodar apenas a 2,7 GHz, ~ 43 MiB / s de dados compactados. Eu uso sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'para ajustá-los. Provavelmente, essas chamadas frequentes do sistema não parecem reais vinculadas à CPU trabalho para a unidade de gerenciamento de energia.)

TL: DR: zcat | tail -cé vinculado à CPU mesmo em uma CPU rápida, a menos que você tenha discos muito lentos. O gzip usou 100% da CPU na qual executou (e executou 1,81 instruções por relógio, de acordo com perf), e tailusou 0,162 da CPU na qual executou (0,58 IPC). Caso contrário, o sistema estava praticamente inativo.

Estou usando o Linux 4.14.11-1-ARCH, que tem o KPTI ativado por padrão para solucionar o Meltdown, portanto, todas as writechamadas de sistema gzipsão mais caras do que costumavam ser: /


Ter a busca incorporada unzipou zcat(mas ainda usando a zlibfunção de decodificação regular ) salvaria todas essas gravações de pipe e faria com que as CPUs Skylake funcionassem a toda velocidade. (Esse downclock para alguns tipos de carga é exclusivo do Intel Skylake e posterior, que descarregou a tomada de decisão da frequência da CPU do SO, porque eles têm mais dados sobre o que a CPU está fazendo e podem aumentar / diminuir mais rapidamente. normalmente bom, mas aqui a Skylake não aumenta sua velocidade máxima com uma configuração de governador mais conservadora).

Nenhuma chamada do sistema, apenas reescrever um buffer que se encaixa no cache L2 até você alcançar a posição de byte inicial desejada, provavelmente faria pelo menos uma diferença de alguns%. Talvez até 10%, mas estou inventando números aqui. Não detalhei zlibdetalhadamente o tamanho de um espaço ocupado em cache e o quanto a liberação TLB (e, portanto, a liberação de cache de cache) em todas as chamadas do sistema prejudica o KPTI ativado.


Existem alguns projetos de software que adicionam um índice de busca ao formato de arquivo gzip . Isso não ajuda se você não conseguir que alguém gere arquivos compactados procuráveis ​​para você, mas outros futuros leitores podem se beneficiar.

Presumivelmente, nenhum desses projetos tem uma função de decodificação que sabe pular um fluxo Deflate sem um índice, porque eles são projetados para funcionar apenas quando um índice está disponível.

  • GZinga: Gzip Seekable e Splittable . Permite tamanhos de bloco grandes.
  • BGZF - GZIP bloqueado, maior e melhor! (o tamanho máximo pequeno do bloco = 64kiB afeta um pouco as taxas de compactação. Projetado para uso com dados de bioinformática, como o FASTA, que geralmente são usados ​​sem compactação, com suporte transparente em algumas bibliotecas python.)
Peter Cordes
fonte
1

Você pode abrir o arquivo zip em uma sessão python, zf = zipfile.ZipFile(filename, 'r', allowZip64=True)e, uma vez aberto, pode abrir, para leitura, qualquer arquivo dentro do arquivo zip e linhas de leitura, etc., como se fosse um arquivo normal.

Steve Barnes
fonte