¿Cómo dividir eficientemente un archivo de texto grande sin dividir registros multilínea?

9

Tengo un archivo de texto grande (~ 50 Gb cuando gz'ed). El archivo contiene 4*Nlíneas o Nregistros; Es decir, cada registro consta de 4 líneas. Me gustaría dividir este archivo en 4 archivos más pequeños, cada uno con un tamaño aproximado del 25% del archivo de entrada. ¿Cómo puedo dividir el archivo en el límite de registro?

Un enfoque ingenuo sería zcat file | wc -lobtener el recuento de líneas, dividir ese número entre 4 y luego usarlo split -l <number> file. Sin embargo, esto pasa dos veces por el archivo y el conteo de línea es extremadamente lento (36 minutos). ¿Hay una mejor manera?

Esto se acerca pero no es lo que estoy buscando. La respuesta aceptada también cuenta una línea.

EDITAR:

El archivo contiene datos de secuencia en formato fastq. Dos registros se ven así (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

La primera línea de cada registro comienza con a @.

EDIT2:

zcat file > /dev/null toma 31 minutos

EDITAR3: Solo comienza la primera línea @. Ninguno de los otros lo hará nunca. Ver aquí . Los registros deben mantenerse en orden. No está bien agregar nada al archivo resultante.

Rolf
fuente
¿Cuánto tiempo toma una sola zcat file > /dev/null?
choroba
¿Puede proporcionar una pequeña muestra del archivo en cuestión?
FloHelf
Dices que cada registro comienza con @y también que hay 4 líneas por registro. ¿Son ambos absolutos? - ¿y pueden comenzar las líneas 2,3,4 @? ¿y hay algún encabezado sin registro de líneas de pie de página en el archivo?
Peter.O
1
¿Está buscando una solución que maneje entradas comprimidas y / o produzca salidas comprimidas? ¿Estás buscando cuatro archivos comprimidos del mismo tamaño?
Stephen Kitt

Respuestas:

4

No creo que puedas hacer esto, no de manera confiable, y no de la manera que lo pides. La cuestión es que la relación de compresión del archivo probablemente no se distribuirá de manera uniforme de la cabeza a la cola: el algoritmo de compresión se aplicará mejor a algunas partes que a otras. Así es como funciona. Por lo tanto, no puede factorizar su división en el tamaño del archivo comprimido.

Además, gzipsimplemente no admite el almacenamiento del tamaño original de archivos comprimidos de más de 4 gbs de tamaño, no puede manejarlo. Por lo tanto, no puede consultar el archivo para obtener un tamaño confiable, porque lo engañará.

Lo de 4 líneas: eso es bastante fácil, de verdad. Lo de los 4 archivos: simplemente no sé cómo podría hacerlo de manera confiable y con una distribución uniforme sin extraer primero el archivo para obtener su tamaño sin comprimir. No creo que puedas porque lo intenté.

Sin embargo, lo que puede hacer es establecer un tamaño máximo para los archivos de salida divididos y asegurarse de que siempre se rompan en las barreras de registro. Eso puedes hacer fácilmente. Aquí hay una pequeña secuencia de comandos que lo hará extrayendo el gziparchivo y canalizando el contenido a través de algunos ddbuffers de tubería explícitos con count=$rptargumentos específicos , antes de pasarlo lz4para descomprimir / recomprimir cada archivo sobre la marcha. También agregué algunos pequeños teetrucos de tubería para imprimir las últimas cuatro líneas para cada segmento para stderr también.

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

Eso continuará hasta que haya manejado todas las entradas. No intenta dividirlo en algún porcentaje, que no puede obtener, sino que lo divide por un recuento máximo de bytes sin procesar por división. Y de todos modos, una gran parte de su problema es que no puede obtener un tamaño confiable en su archivo porque es demasiado grande, haga lo que haga, no vuelva a hacer eso, haga que las divisiones de menos de 4 gbs por pieza sean redondas. , tal vez. Este pequeño script, al menos, le permite hacer esto sin tener que escribir un byte sin comprimir en el disco.

Aquí hay una versión más corta despojada de lo esencial: no agrega todas las cosas del informe:

(       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

Hace todo lo mismo que el primero, principalmente, simplemente no tiene mucho que decir al respecto. Además, hay menos desorden, por lo que es más fácil ver lo que está sucediendo, tal vez.

La IFS=cuestión es solo manejar una readlínea por iteración. Somos readuno porque necesitamos que nuestro ciclo finalice cuando finaliza la entrada. Esto depende del tamaño de su registro , que, según su ejemplo, es de 354 bytes por. Creé un gziparchivo de 4 + gb con algunos datos aleatorios para probarlo.

Los datos aleatorios se obtuvieron de esta manera:

(       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

... pero tal vez no tenga que preocuparse tanto por eso, ya que ya tiene los datos y todo. De vuelta a la solución ...

Básicamente pigz, que parece descomprimirse un poco más rápido que lo hace zcat, canaliza el flujo sin comprimir y ddalmacena en búfer la salida en bloques de escritura de un tamaño específico en un múltiplo de 354 bytes. El bucle readuna $linevez cada iteración a la prueba de que la entrada todavía está llegando, que será printfdespués printfen lz4antes de que otro ddse llama para leer bloques de tamaño específicamente a un múltiplo de 354 bytes - para sincronizar con el almacenamiento en búfer ddproceso - para la duración. Habrá una breve lectura por iteración debido a la inicial read $line, pero eso no importa, porque de lz4todos modos estamos imprimiendo eso en nuestro proceso de recopilación.

Lo configuré para que cada iteración lea aproximadamente 1 gb de datos sin comprimir y comprima ese in-stream a alrededor de 650Mb más o menos. lz4es mucho más rápido que casi cualquier otro método de compresión útil, razón por la cual lo elegí aquí porque no me gusta esperar. xzSin embargo, probablemente haría un trabajo mucho mejor en la compresión real. Sin lz4embargo, una cosa es que a menudo puede descomprimirse a velocidades cercanas a la RAM, lo que significa que muchas veces puede descomprimir un lz4archivo tan rápido como podría escribirlo en la memoria de todos modos.

El grande hace algunos informes por iteración. Ambos bucles imprimirán ddel informe sobre la cantidad de bytes sin procesar transferidos y la velocidad, etc. El bucle grande también imprimirá las últimas 4 líneas de entrada por ciclo, y un recuento de bytes para el mismo, seguido de uno lsdel directorio en el que escribo los lz4archivos. Aquí hay un par de rondas de salida:

/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
mikeserv
fuente
gzip -lsolo funciona para <2GiB archivos sin comprimir IIRC (de todos modos, algo más pequeño que el archivo OP).
Stéphane Chazelas
@ StéphaneChazelas - maldición. Esa es la única forma en que podría pensar en obtener un tamaño sin comprimir. Sin eso, esto no funciona en absoluto.
mikeserv
4

Dividir archivos en los límites de registro es realmente muy fácil, sin ningún código:

zcat your_file.gz | split -l 10000 - output_name_

Esto creará archivos de salida de 10000 líneas cada uno, con nombres output_name_aa, output_name_ab, output_name_ac, ... Con una entrada tan grande como la suya, esto le dará muchos archivos de salida. Reemplace 10000con cualquier múltiplo de cuatro, y puede hacer que los archivos de salida sean tan grandes o pequeños como desee. Desafortunadamente, como con las otras respuestas, no hay una buena manera de garantizar que obtendrá el número deseado de archivos de salida (aproximadamente) del mismo tamaño sin hacer algunas suposiciones sobre la entrada. (O, de hecho, pasar todo el proceso wc). Si sus registros tienen aproximadamente el mismo tamaño (o al menos, se distribuyen de manera más o menos uniforme), puede intentar obtener una estimación como esta:

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

Eso le dirá el tamaño comprimido de los primeros 1000 registros de su archivo. Basado en eso, probablemente pueda llegar a una estimación de cuántas filas desea en cada archivo para terminar con cuatro archivos. (Si no desea que quede un quinto archivo degenerado, asegúrese de aumentar un poco su estimación, o esté preparado para pegar el quinto archivo en la cola del cuarto).

Editar: Aquí hay un truco más, suponiendo que desea archivos de salida comprimidos:

#!/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

Esto creará muchos archivos más pequeños y luego los juntará rápidamente. (Es posible que tenga que ajustar el parámetro -l dependiendo de la longitud de las líneas en sus archivos). Se supone que tiene una versión relativamente reciente de los coreutils de GNU (para split --filter) y aproximadamente el 130% del tamaño de su archivo de entrada en espacio libre en disco. Sustituya pigz / unpigz por gzip / zcat si no los tiene. He oído que algunas bibliotecas de software (¿Java?) No pueden manejar archivos gzip concatenados de esta manera, pero hasta ahora no he tenido ningún problema. (Pigz usa el mismo truco para paralelizar la compresión).

Dibujó
fuente
Si tiene instalado pigz, puede acelerar un poco las cosas sustituyendo 'pigz -cd' por 'zcat'.
Drew
2
Ah, acabo de notar ahora que ya mencionaste la división en la pregunta. Pero en realidad, casi cualquier solución hará lo mismo que dividir bajo el capó. La parte difícil es calcular cuántas filas necesitas poner en cada archivo.
Dibujó
3

Por lo que deduzco después de verificar la esfera de google, y luego de probar un .gzarchivo de 7.8 GiB , parece que los metadatos del tamaño original del archivo sin comprimir no son precisos (es decir, incorrectos ) para .gzarchivos grandes (mayores de 4GiB (quizás 2GiB para algunos versiones de gzip).
Re. mi prueba de metadatos de 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

Por lo tanto, parece que no es posible determinar el tamaño sin comprimir sin descomprimirlo (¡lo cual es un poco tosco, por decir lo menos!)

De todos modos, aquí hay una manera de dividir un archivo sin comprimir en los límites del registro, donde cada registro contiene 4 líneas .

Utiliza el tamaño del archivo en bytes (vía stat) y awkcontando bytes (no caracteres). Si los finales de línea son o no LF| CREl | CRLF, este script maneja la longitud final de la línea a través de la variable 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

A continuación se muestra la prueba que utilicé para verificar que el recuento de líneas de cada archivo sea 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

Prueba de salida:

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 fue generado por:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile
Peter.O
fuente
2

¡Esto no pretende ser una respuesta seria! Solo he estado jugando flexy esto probablemente no funcionará en un archivo de entrada con ~ 50 Gb (si es que lo hace, en datos de entrada más grandes que mi archivo de prueba):

Esto funciona para mí en un archivo ~ 1Gb input.txt :

Dado el flexarchivo 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;
    }
}

generando lex.yy.c y compilándolo al splitterbinario con:

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

Uso:

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

Tiempo de ejecución 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
Flo Mismo
fuente
El lexing real aquí es tan simple que realmente no te beneficias del lex. Simplemente llame getc(stream)y aplique una lógica simple. Además, ¿sabes que el. El carácter (punto) regex en (f) lex coincide con cualquier carácter excepto la nueva línea , ¿verdad? Mientras que estos registros son de varias líneas.
Kaz
@Kaz Si bien sus declaraciones son generalmente corrientes, esto realmente funciona con los datos proporcionados en la P.
FloHelfelf
Solo accidentalmente, porque hay una regla predeterminada cuando nada coincide: ¡consuma un carácter e imprímalo en la salida! En otras palabras, puede cambiar su archivo simplemente con una regla que reconozca el @carácter y luego dejar que la regla predeterminada copie los datos. Ahora tiene su regla copiando parte de los datos como un token grande, y luego la regla predeterminada obtiene la segunda línea de un carácter a la vez.
Kaz
Gracias por aclararlo. Me pregunto cómo resolverías esta tarea txr.
FloHelf 24/15
No estoy seguro de que lo haría porque la tarea es hacer una cosa muy simple con una gran cantidad de datos, lo más rápido posible.
Kaz
1

Aquí hay una solución en Python que hace que una pasada sobre el archivo de entrada escriba los archivos de salida a medida que avanza.

Una característica sobre el uso wc -les que está asumiendo que cada uno de los registros aquí tiene el mismo tamaño. Eso puede ser cierto aquí, pero la solución a continuación funciona incluso cuando ese no es el caso. Básicamente está utilizando wc -co el número de bytes en el archivo. En Python, esto se hace a través de os.stat ()

Así es como funciona el programa. Primero calculamos los puntos de división ideales como desplazamientos de bytes. Luego, lee las líneas del archivo de entrada que se escriben en el archivo de salida apropiado. Cuando vea que ha excedido el siguiente punto de división óptimo y se encuentra en un límite de registro, cierre el último archivo de salida y abra el siguiente.

El programa es óptimo en este sentido, lee los bytes del archivo de entrada una vez; Obtener el tamaño del archivo no requiere leer los datos del archivo. El almacenamiento necesario es proporcional al tamaño de una línea. Pero Python o el sistema presumiblemente tienen buffers de archivos razonables para acelerar la E / S.

He agregado parámetros para cuántos archivos dividir y cuál es el tamaño del registro en caso de que desee ajustar esto en el futuro.

Y claramente esto también podría traducirse a otros lenguajes de programación.

Otra cosa, no estoy seguro de si Windows con su crlf maneja la longitud de la línea correctamente como lo hace en los sistemas Unix-y. Si len () está desactivado por uno aquí, espero que sea obvio cómo ajustar el 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))
rocoso
fuente
No se divide en un límite de registro. p.ej. La primera división del subarchivo ocurre después de la tercera línea con esta entradaprintf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter
1

El usuario FloHelf mismo parecía curioso acerca de una solución TXR . Aquí hay uno que usa el 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. Por la misma razón, popes importante agregar cada tupla de la lista perezosa de tuplas, de modo que se consuma la lista perezosa. No debemos retener una referencia al comienzo de esa lista porque la memoria crecerá a medida que avancemos por el archivo.

  2. (seek-stream fo 0 :from-current)es un caso no operativo seek-stream, que se vuelve útil al devolver la posición actual.

  3. Rendimiento: no lo menciones. Utilizable, pero no traerá trofeos a casa.

  4. Como solo hacemos la verificación del tamaño cada 1000 tuplas, podríamos hacer que el tamaño de la tupla sea de 4000 líneas.

Kaz
fuente
0

Si no necesita que los nuevos archivos sean fragmentos contiguos del archivo original, puede hacerlo completamente sedde la siguiente manera:

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

Esto -nevita que imprima cada línea, y cada uno de los -escripts está esencialmente haciendo lo mismo. 1~16coincide con la primera línea y cada 16 líneas después. ,+3significa unir las siguientes tres líneas después de cada una de ellas. w1.txtdice escribir todas esas líneas en el archivo 1.txt. Esto toma cada 4to grupo de 4 líneas y lo escribe en un archivo, comenzando con el primer grupo de 4 líneas. Los otros tres comandos hacen lo mismo, pero cada uno se desplaza hacia adelante por 4 líneas y escribe en un archivo diferente.

Esto se romperá horriblemente si el archivo no coincide exactamente con la especificación que estableció, pero de lo contrario debería funcionar como usted lo pretendía. No lo he perfilado, así que no sé qué tan eficiente será, pero sedes razonablemente eficiente en la edición de secuencias.

Erik
fuente