Como dividir eficientemente um arquivo de texto grande sem dividir registros de várias linhas?


9

Eu tenho um grande arquivo de texto (~ 50 GB quando gz'ed). O arquivo contém 4*Nlinhas ou Nregistros; 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 -lobter 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.


Quanto tempo leva um único zcat file > /dev/null?
choroba

Você pode fornecer uma pequena amostra do arquivo em questão?
FloHimself

Você diz que todo registro começa com @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?
Peter.O

1
Você está procurando uma solução que lide com entrada compactada e / ou produz saída compactada? Você está procurando quatro arquivos compactados de tamanho igual?
Stephen Kitt

Respostas:


4

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, gzipsimplesmente 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 gziparquivo morto e canalizando o conteúdo através de alguns ddbuffers de pipe explícitos com count=$rptargumentos específicos , antes de repassá-lo lz4para descomprimir / recomprimir cada arquivo em tempo real. Também fiz alguns teetruques para imprimir as últimas quatro linhas de cada segmento para stderr também.

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)

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:

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz

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 uma readlinha por iteração. Nós readum 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 um gziparquivo de 4 + gb com alguns dados aleatórios para testá-lo.

Os dados aleatórios foram obtidos desta maneira:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

... 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 faz zcat- canaliza o fluxo não compactado e os ddbuffers que saem em blocos de gravação dimensionados especificamente para um múltiplo de 354 bytes. O loop será repetido readuma $linevez a cada iteração para testar se a entrada ainda está chegando, que será printfposteriormenteprintf no lz4antes de um outro ddé chamada para ler blocos dimensionado especificamente a um múltiplo de 354 bytes - sincronizar com o tamponamento ddprocesso - para a duração. Haverá uma leitura curta por iteração por causa da inicial read $line- mas isso não importa, porque estamos imprimindo isso no lz4- 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. xzprovavelmente 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 um lz4arquivo 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 ddo 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 um lsdiretório no qual escrevo os lz4arquivos. Aqui estão algumas rodadas de saída:

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2

gzip -lsó funciona para arquivos não compactados <2GiB IIRC (de qualquer maneira, menor que o arquivo do OP).
Stéphane Chazelas

@ StéphaneChazelas - caramba. Essa é a única maneira que eu poderia imaginar em obter um tamanho não compactado. Sem isso, isso não funciona.
mikeserv

4

Dividir arquivos nos limites do registro é realmente muito fácil, sem nenhum código:

zcat your_file.gz | split -l 10000 - output_name_

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 10000por 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 toda wc.) Se seus registros tiverem tamanho aproximadamente igual (ou pelo menos distribuídos de maneira uniforme), você pode tentar criar uma estimativa como esta:

zcat your_file.gz | head -n4000 | gzip | wc -c

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:

#!/bin/sh

base=$(basename $1 .gz)
unpigz -c $1 | split -l 100000 --filter='pigz -c > _$FILE.gz' - ${base}_

batch=$((`ls _*.gz | wc -l` / 4 + 1))
for i in `seq 1 4`; do
  files=`ls _*.gz | head -$batch`
  cat $files > ${base}_$i.gz && rm $files
done

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.)


Se você possui o pigz instalado, pode acelerar um pouco as coisas substituindo 'pigz -cd' por 'zcat'.
Tirou

2
Ah, acabei de notar agora que você já mencionou a divisão na pergunta. Mas, na verdade, praticamente qualquer solução fará a mesma coisa que dividir sob o capô. A parte difícil é descobrir quantas linhas você precisa colocar em cada arquivo.
Drew

3

Pelo que entendi depois de verificar o google-sphere e testar mais um .gzarquivo de 7,8 GiB , parece que os metadados do tamanho do arquivo original não compactado não são precisos (isto é, incorretos ) para .gzarquivos grandes (maiores que 4GiB (talvez 2GiB para alguns versões de gzip)
.Re. meu teste dos metadados do gzip:

* The compressed.gz file is  7.8 GiB ( 8353115038 bytes) 
* The uncompressed  file is 18.1 GiB (19436487168 bytes)
* The metadata says file is  2.1 GiB ( 2256623616 bytes) uncompressed

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 com awkcontagem de bytes (não caracteres). Se o final da linha é ou não LF| CR| CRLF, esse script lida com o comprimento final da linha por meio da variável incorporada RT).

LC_ALL=C gawk 'BEGIN{"stat -c %s "ARGV[1] | getline inSize
                      segSiz=int(inSize/4)+((inSize%4)==0?0:1)
                      ouSplit=segSiz; segNb=0 }
               { lnb++; bytCt+=(length+length(RT))
                 print $0 > ARGV[1]"."segNb
                 if( lnb!=4 ) next
                 lnb=0
                 if( bytCt>=ouSplit ){ segNb++; ouSplit+=segSiz }
               }' myfile

Abaixo está o teste que eu usei para verificar se a contagem de linhas de cada arquivo é mod 4 == 0

for i in myfile  myfile.{0..3}; do
    lc=$(<"$i" wc -l)
    printf '%s\t%s\t' "$i" $lc; 
    (( $(echo $lc"%4" | bc) )) && echo "Error: mod 4 remainder !" || echo 'mod 4 ok'  
done | column -ts$'\t' ;echo

Saída de teste:

myfile    1827904  mod 4 ok
myfile.0  456976   mod 4 ok
myfile.1  456976   mod 4 ok
myfile.2  456976   mod 4 ok
myfile.3  456976   mod 4 ok

myfile foi gerado por:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile

2

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 flexarquivo de entrada splitter.l :

%{
#include <stdio.h>
extern FILE* yyin;
extern FILE* yyout;

int input_size = 0;

int part_num;
int part_num_max;
char **part_names;
%}

%%
@.+ {
        if (ftell(yyout) >= input_size / part_num_max) {
            fclose(yyout);
            if ((yyout = fopen(part_names[++part_num], "w")) == 0) {
                exit(1);
            }
        }
        fprintf(yyout, "%s", yytext);
    }
%%

int main(int argc, char *argv[]) {

    if (argc < 2) {
        return 1;
    } else if ((yyin = fopen(argv[1], "r")) == 0) {
        return 1;
    } else if ((yyout = fopen(argv[2], "w")) == 0) {
        fclose(yyin);
        return 1;
    } else {

        fseek(yyin, 0L, SEEK_END);
        input_size = ftell(yyin);
        rewind(yyin);

        part_num = 0;
        part_num_max = argc - 2;
        part_names = argv + 2;

        yylex();

        fclose(yyin);
        fclose(yyout);
        return 0;
    }
}

gerando lex.yy.c e compilando-o no splitterbinário com:

$ flex splitter.l && gcc lex.yy.c -ll -o splitter

Uso:

$ ./splitter input.txt output.part1 output.part2 output.part3 output.part4

Tempo de execução para 1Gb input.txt :

$ time ./splitter input.txt output.part1 output.part2 output.part3 output.part4

real    2m43.640s
user    0m48.100s
sys     0m1.084s

O lexing real aqui é tão simples que você realmente não se beneficia dele. Basta ligar 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.
Kaz

@Kaz Enquanto suas declarações são geralmente corrent, isso realmente funciona com os dados fornecidos em Q.
FloHimself

Apenas acidentalmente, porque existe uma regra padrão quando nada corresponde: consuma um caractere e imprima-o na saída! Em outras rwords, você pode fazer a troca de arquivos simplesmente com uma regra que reconheça o @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.
Kaz

Obrigado por esclarecer. Gostaria de saber como você resolveria essa tarefa txr.
FloHimself

Não tenho certeza, porque a tarefa é fazer uma coisa muito simples com uma grande quantidade de dados, o mais rápido possível.
Kaz

1

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 usando wc -cou 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.

#!/usr/bin/env python
import os

# Adjust these
filename = 'file.txt'
rec_size = 4
file_splits = 4

size = os.stat(filename).st_size
splits = [(i+1)*size/file_splits for i in range(file_splits)]
with open(filename, 'r') as fd:
    linecount = 0
    i = 0 # File split number
    out = open('file%d.txt' % i, 'w')
    offset = 0  # byte offset of where we are in the file: 0..size
    r = 0 # where we are in the record: 0..rec_size-1
    for line in fd:
        linecount += 1
        r = (r+1) % rec_size
        if offset + len(line) > splits[i] and r == 1 :
            out.close()
            i += 1
            out = open('file%d.txt' % i, 'w')
        out.write(line)
        offset += len(line)
    out.close()
    print("file %s has %d lines" % (filename, linecount))

Não está se dividindo em um limite recorde. por exemplo. A primeira divisão sub arquivo acontece após a 3ª linha com esta entradaprintf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O

1

O usuário FloHimself parecia curioso sobre uma solução TXR . Aqui está um usando o TXR Lisp incorporado :

(defvar splits 4)
(defvar name "data")

(let* ((fi (open-file name "r"))                 ;; input stream
       (rc (tuples 4 (get-lines fi)))            ;; lazy list of 4-tuples
       (sz (/ (prop (stat name) :size) splits))  ;; split size
       (i 1)                                     ;; split enumerator
       (n 0)                                     ;; tuplecounter within split
       (no `@name.@i`)                           ;; output split file name
       (fo (open-file no "w")))                  ;; output stream
  (whilet ((r (pop rc)))  ;; pop each 4-tuple
    (put-lines r fo) ;; send 4-tuple into output file
    ;; if not on the last split, every 1000 tuples, check the output file
    ;; size with stat and switch to next split if necessary.
    (when (and (< i splits)
               (> (inc n) 1000)
               (>= (seek-stream fo 0 :from-current) sz))
      (close-stream fo)
      (set fo (open-file (set no `@name.@(inc i)`) "w")
           n 0)))
  (close-stream fo))

Notas:

  1. 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.

  2. (seek-stream fo 0 :from-current)é um caso não operacional de seek-stream, o que se torna útil retornando a posição atual.

  3. Performance: não mencione. Utilizável, mas não trará nenhum troféu para casa.

  4. Como só fazemos a verificação do tamanho a cada 1000 tuplas, podemos fazer o tamanho da 4000 em quatro linhas.


0

Se você não precisar que os novos arquivos sejam pedaços contíguos do arquivo original, faça isso inteiramente sedda seguinte maneira:

sed -n -e '1~16,+3w1.txt' -e '5~16,+3w2.txt' -e '9~16,+3w3.txt' -e '13~16,+3w4.txt'

O -nimpede de imprimir cada linha, e cada um dos -escripts está essencialmente fazendo a mesma coisa. 1~16corresponde à primeira linha e a cada 16ª linha depois. ,+3significa combinar as próximas três linhas após cada uma delas. w1.txtdiz escrever todas essas linhas no arquivo 1.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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.