Eu tenho um grande arquivo de texto (~ 50 GB quando gz'ed). O arquivo contém 4*N
linhas ou N
registros; ou seja, todo registro consiste em 4 linhas. Gostaria de dividir este arquivo em 4 arquivos menores, cada um com aproximadamente 25% do arquivo de entrada. Como posso dividir o arquivo no limite do registro?
Uma abordagem ingênua seria zcat file | wc -l
obter a contagem de linhas, dividir esse número por 4 e depois usá-lo split -l <number> file
. No entanto, isso passa por cima do arquivo duas vezes e a contagem de linhas é extremamente lenta (36 minutos). Existe uma maneira melhor?
Isso chega perto, mas não é o que estou procurando. A resposta aceita também faz uma contagem de linhas.
EDITAR:
O arquivo contém dados de seqüenciamento no formato fastq. Dois registros são assim (anonimizados):
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
A primeira linha de cada registro começa com a @
.
EDIT2:
zcat file > /dev/null
leva 31 minutos.
EDIT3:
Somente a primeira linha começa com @
. Nenhum dos outros jamais. Veja aqui . Os registros precisam permanecer em ordem. Não é permitido adicionar nada ao arquivo resultante.
zcat file > /dev/null
?@
e também que existem 4 linhas por registro. Ambos são absolutos? - e as linhas 2,3,4 podem começar@
? e há algum cabeçalho sem registro de linhas de rodapé no arquivo?Respostas:
Eu não acho que você possa fazer isso - não de maneira confiável e não do jeito que você pergunta. O problema é que a taxa de compactação do arquivo provavelmente não será distribuída igualmente da cabeça à cauda - o algoritmo de compactação se aplicará melhor a algumas partes do que a outras. É assim que funciona. E, portanto, você não pode fatorar sua divisão no tamanho do arquivo compactado.
Além disso,
gzip
simplesmente não suporta armazenar o tamanho original de arquivos compactados com mais de 4 gbs de tamanho - ele não pode lidar com isso. Portanto, você não pode consultar o arquivo para obter um tamanho confiável - porque isso o enganará.A coisa de 4 linhas - é bem fácil, na verdade. A coisa dos 4 arquivos - simplesmente não sei como você poderia fazer isso de maneira confiável e com uma distribuição uniforme, sem primeiro extrair o arquivo para obter seu tamanho descompactado. Eu não acho que você pode, porque eu tentei.
No entanto, o que você pode fazer é definir um tamanho máximo para arquivos de saída divididos e garantir que eles sempre sejam quebrados em barreiras de registro. Isso você pode fazer facilmente. Aqui está um pequeno script que fará isso extraindo o
gzip
arquivo morto e canalizando o conteúdo através de algunsdd
buffers de pipe explícitos comcount=$rpt
argumentos específicos , antes de repassá-lolz4
para descomprimir / recomprimir cada arquivo em tempo real. Também fiz algunstee
truques para imprimir as últimas quatro linhas de cada segmento para stderr também.Isso continuará até que ele lide com todas as entradas. Ele não tenta dividi-lo por alguma porcentagem - o que não pode ser obtido -, mas o divide por uma contagem máxima de bytes brutos por divisão. De qualquer forma, grande parte do seu problema é que você não pode obter um tamanho confiável em seu arquivo porque é muito grande - faça o que fizer, não faça isso de novo - faça com que as divisões sejam inferiores a 4 gbs por peça. , talvez. Este pequeno script, pelo menos, permite que você faça isso sem precisar gravar um byte descompactado no disco.
Aqui está uma versão mais curta, simplificada - ela não adiciona todo o material do relatório:
Ele faz as mesmas coisas que o primeiro, principalmente, mas não tem muito a dizer sobre isso. Além disso, há menos confusão, por isso é mais fácil ver o que está acontecendo, talvez.
A
IFS=
questão é apenas lidar com umaread
linha por iteração. Nósread
um porque precisamos que nosso loop termine quando a entrada terminar. Isso depende do tamanho do seu registro - que, por exemplo, é de 354 bytes por. Criei umgzip
arquivo de 4 + gb com alguns dados aleatórios para testá-lo.Os dados aleatórios foram obtidos desta maneira:
... mas talvez você não precise se preocupar muito com isso, já que você já tem os dados e tudo. Voltar para a solução ...
Basicamente
pigz
- o que parece descomprimir um pouco mais rápido do que o fazzcat
- canaliza o fluxo não compactado e osdd
buffers que saem em blocos de gravação dimensionados especificamente para um múltiplo de 354 bytes. O loop será repetidoread
uma$line
vez a cada iteração para testar se a entrada ainda está chegando, que seráprintf
posteriormenteprintf
nolz4
antes de um outrodd
é chamada para ler blocos dimensionado especificamente a um múltiplo de 354 bytes - sincronizar com o tamponamentodd
processo - para a duração. Haverá uma leitura curta por iteração por causa da inicialread $line
- mas isso não importa, porque estamos imprimindo isso nolz4
- nosso processo de coletor - de qualquer maneira.Eu o configurei para que cada iteração leia aproximadamente 1 gb de dados não compactados e comprima esse in-stream para cerca de 650 Mb ou mais.
lz4
é muito mais rápido do que qualquer outro método de compactação útil - e foi por isso que o escolhi aqui porque não gosto de esperar.xz
provavelmente faria um trabalho muito melhor na compressão real. Uma coisa élz4
, porém, que muitas vezes é possível descompactar a velocidades próximas à RAM - o que significa que muitas vezes você pode descomprimir umlz4
arquivo tão rápido quanto seria possível gravá-lo na memória.O grande faz alguns relatórios por iteração. Os dois loops imprimirão
dd
o relatório sobre o número de bytes brutos transferidos, a velocidade e assim por diante. O loop grande também imprimirá as últimas 4 linhas de entrada por ciclo, e uma contagem de bytes para o mesmo, seguida por umls
diretório no qual escrevo oslz4
arquivos. Aqui estão algumas rodadas de saída:fonte
gzip -l
só funciona para arquivos não compactados <2GiB IIRC (de qualquer maneira, menor que o arquivo do OP).Dividir arquivos nos limites do registro é realmente muito fácil, sem nenhum código:
Isso criará arquivos de saída de 10000 linhas cada, com os nomes output_name_aa, output_name_ab, output_name_ac, ... Com uma entrada tão grande quanto a sua, isso fornecerá muitos arquivos de saída. Substitua
10000
por qualquer múltiplo de quatro, e você pode tornar os arquivos de saída tão grandes ou pequenos quanto desejar. Infelizmente, como nas outras respostas, não há uma boa maneira de garantir que você obtenha o número desejado de (aproximadamente) tamanho igual de arquivos de saída sem fazer algumas suposições sobre a entrada. (Ou, na verdade, analisando a coisa todawc
.) Se seus registros tiverem tamanho aproximadamente igual (ou pelo menos distribuídos de maneira uniforme), você pode tentar criar uma estimativa como esta:Isso informará o tamanho compactado dos primeiros 1000 registros do seu arquivo. Com base nisso, você provavelmente pode ter uma estimativa de quantas linhas deseja em cada arquivo para terminar com quatro arquivos. (Se você não quiser um quinto arquivo degenerado, restaure um pouco sua estimativa ou esteja preparado para prender o quinto arquivo no final do quarto.)
Edit: Aqui está mais um truque, supondo que você queira arquivos de saída compactados:
Isso criará muitos arquivos menores e os reunirá rapidamente. (Você pode precisar ajustar o parâmetro -l dependendo de quanto tempo as linhas de seus arquivos tiverem.) Supõe-se que você tenha uma versão relativamente recente do GNU coreutils (para split --filter) e cerca de 130% do tamanho do arquivo de entrada em Espaço livre em disco. Substitua gzip / zcat por pigz / unpigz, se você não os tiver. Ouvi dizer que algumas bibliotecas de software (Java?) Não conseguem lidar com arquivos gzip concatenados dessa maneira, mas ainda não tive problemas com isso. (pigz usa o mesmo truque para paralelizar a compactação.)
fonte
Pelo que entendi depois de verificar o google-sphere e testar mais um
.gz
arquivo de 7,8 GiB , parece que os metadados do tamanho do arquivo original não compactado não são precisos (isto é, incorretos ) para.gz
arquivos grandes (maiores que 4GiB (talvez 2GiB para alguns versões degzip
).Re. meu teste dos metadados do gzip:
Portanto, parece que não é possível determinar o tamanho não compactado sem realmente descompactá-lo (o que é um pouco difícil, para dizer o mínimo!)
De qualquer forma, aqui está uma maneira de dividir um arquivo não compactado nos limites do registro, onde cada registro contém 4 linhas .
Ele usa o tamanho do arquivo em bytes (via
stat
) e comawk
contagem de bytes (não caracteres). Se o final da linha é ou nãoLF
|CR
|CRLF
, esse script lida com o comprimento final da linha por meio da variável incorporadaRT
).Abaixo está o teste que eu usei para verificar se a contagem de linhas de cada arquivo é
mod 4 == 0
Saída de teste:
myfile
foi gerado por:fonte
Isso não pretende ser uma resposta séria! Eu só estava brincando comflex
e isso provavelmente não funcionará em um arquivo de entrada com ~ 50Gb (se houver, em dados de entrada maiores que o meu arquivo de teste):Isso funciona para mim em um arquivo ~ 1Gb input.txt :
Dado o
flex
arquivo de entrada splitter.l :gerando lex.yy.c e compilando-o no
splitter
binário com:Uso:
Tempo de execução para 1Gb input.txt :
fonte
getc(stream)
e aplicar alguma lógica simples. Além disso, você sabe que o. (ponto) o caractere regex em (f) lex corresponde a qualquer caractere, exceto nova linha , certo? Considerando que esses registros são multi-line.@
caractere e depois permitir que a regra padrão copie os dados. Agora você tem sua regra copiando parte dos dados como um grande token e, em seguida, a regra padrão obtendo a segunda linha, um caractere de cada vez.txr
.Aqui está uma solução em Python que faz uma passagem pelo arquivo de entrada gravando os arquivos de saída à medida que avança.
Um recurso sobre o uso
wc -l
é que você está assumindo que cada um dos registros aqui tem o mesmo tamanho. Isso pode ser verdade aqui, mas a solução abaixo funciona mesmo quando não é esse o caso. É basicamente usandowc -c
ou o número de bytes no arquivo. No Python, isso é feito via os.stat ()Então, aqui está como o programa funciona. Primeiro calculamos os pontos de divisão ideais como deslocamentos de bytes. Em seguida, você lê as linhas do arquivo de entrada gravadas no arquivo de saída apropriado. Quando você perceber que excedeu o próximo ponto de divisão ideal e estiver em um limite de registro, feche o último arquivo de saída e abra o próximo.
O programa é ideal nesse sentido, ele lê os bytes do arquivo de entrada uma vez; Obter o tamanho do arquivo não requer a leitura dos dados do arquivo. O armazenamento necessário é proporcional ao tamanho de uma linha. Mas Python ou o sistema provavelmente possui buffers de arquivos razoáveis para acelerar a E / S.
Adicionei parâmetros para quantos arquivos dividir e qual é o tamanho do registro, caso você queira ajustar isso no futuro.
E claramente isso também poderia ser traduzido para outras linguagens de programação.
Outra coisa, não tenho certeza se o Windows com seu crlf lida com o comprimento da linha corretamente, como nos sistemas Unix-y. Se len () estiver desativado em um aqui, espero que seja óbvio como ajustar o programa.fonte
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
O usuário FloHimself parecia curioso sobre uma solução TXR . Aqui está um usando o TXR Lisp incorporado :
Notas:
Pelo mesmo motivo,
pop
é importante digitar cada tupla da lista lenta de tuplas, para que a lista lenta seja consumida. Não devemos manter uma referência ao início dessa lista, porque a memória aumentará à medida que marcharmos pelo arquivo.(seek-stream fo 0 :from-current)
é um caso não operacional deseek-stream
, o que se torna útil retornando a posição atual.Performance: não mencione. Utilizável, mas não trará nenhum troféu para casa.
Como só fazemos a verificação do tamanho a cada 1000 tuplas, podemos fazer o tamanho da 4000 em quatro linhas.
fonte
Se você não precisar que os novos arquivos sejam pedaços contíguos do arquivo original, faça isso inteiramente
sed
da seguinte maneira:O
-n
impede de imprimir cada linha, e cada um dos-e
scripts está essencialmente fazendo a mesma coisa.1~16
corresponde à primeira linha e a cada 16ª linha depois.,+3
significa combinar as próximas três linhas após cada uma delas.w1.txt
diz escrever todas essas linhas no arquivo1.txt
. Isso pega cada quarto grupo de 4 linhas e grava-o em um arquivo, começando com o primeiro grupo de 4 linhas. Os outros três comandos fazem a mesma coisa, mas cada um deles é deslocado para frente em 4 linhas e gravado em um arquivo diferente.Isso quebrará terrivelmente se o arquivo não corresponder exatamente à especificação que você definiu, mas, caso contrário, deverá funcionar como você deseja. Eu não o perfilei, então não sei o quão eficiente será, mas
sed
é razoavelmente eficiente na edição do fluxo.fonte