Comment diviser efficacement un grand fichier texte sans fractionner des enregistrements multilignes?

9

J'ai un gros fichier texte (~ 50 Go lorsque gz'ed). Le fichier contient des 4*Nlignes ou des Nenregistrements; c'est-à-dire que chaque enregistrement se compose de 4 lignes. Je voudrais diviser ce fichier en 4 fichiers plus petits, chacun représentant environ 25% du fichier d'entrée. Comment puis-je diviser le fichier à la limite de l'enregistrement?

Une approche naïve serait zcat file | wc -ld'obtenir le nombre de lignes, de diviser ce nombre par 4, puis de l'utiliser split -l <number> file. Cependant, cela passe deux fois sur le fichier et le compteur de lignes est extrêmement lent (36 minutes). Y a-t-il une meilleure façon?

Cela se rapproche, mais ce n'est pas ce que je recherche. La réponse acceptée fait également un décompte de lignes.

ÉDITER:

Le fichier contient des données de séquençage au format fastq. Deux enregistrements ressemblent à ceci (anonymisés):

@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 première ligne de chaque enregistrement commence par un @.

EDIT2:

zcat file > /dev/null prend 31 minutes.

EDIT3: Seule la première ligne commence par @. Aucun des autres ne le sera jamais. Voyez ici . Les dossiers doivent rester en ordre. Ce n'est pas correct d'ajouter quoi que ce soit au fichier résultant.

Rolf
la source
Combien de temps dure un seul zcat file > /dev/null?
choroba
Pouvez-vous fournir un petit échantillon du fichier en question?
FloHimself
Vous dites que chaque enregistrement commence par @et aussi qu'il y a 4 lignes par enregistrement. Ces deux sont-ils absolus? - et les lignes 2,3,4 peuvent-elles commencer par @? et y a-t-il un en-tête non enregistré de lignes de pied de page dans le fichier?
Peter.O
1
Êtes-vous à la recherche d'une solution qui gère les entrées compressées et / ou produit des sorties compressées? Recherchez-vous quatre fichiers compressés de taille égale?
Stephen Kitt

Réponses:

4

Je ne pense pas que vous puissiez le faire - pas de manière fiable et pas de la façon dont vous le demandez. Le fait est que le taux de compression de l'archive ne sera probablement pas réparti uniformément de la tête à la queue - l'algorithme de compression s'appliquera mieux à certaines parties qu'à d'autres. Voilà comment ça fonctionne. Et vous ne pouvez donc pas prendre en compte votre division sur la taille du fichier compressé.

De plus, gzipne prend tout simplement pas en charge le stockage de la taille d'origine des fichiers compressés supérieurs à 4 Go - il ne peut pas le gérer. Et vous ne pouvez donc pas interroger l'archive pour obtenir une taille fiable, car cela vous trompera.

La chose à 4 lignes - c'est assez facile, vraiment. La chose à 4 fichiers - je ne sais tout simplement pas comment vous pouvez le faire de manière fiable et avec une distribution uniforme sans d'abord extraire l'archive pour obtenir sa taille non compressée. Je ne pense pas que vous puissiez le faire parce que j'ai essayé.

Cependant, ce que vous pouvez faire, c'est définir une taille maximale pour les fichiers de sortie divisés et assurez-vous qu'ils sont toujours cassés aux barrières d'enregistrement. Vous pouvez facilement le faire. Voici un petit script qui le fera en extrayant l' gziparchive et en canalisant le contenu à travers quelques ddtampons de canal explicites avec des count=$rptarguments spécifiques , avant de le passer lz4pour décompresser / recompresser chaque fichier à la volée. J'ai également ajouté quelques teeastuces pour imprimer les quatre dernières lignes de chaque segment sur stderr également.

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

Cela continuera simplement jusqu'à ce qu'il ait traité toutes les entrées. Il ne tente pas de le diviser par un certain pourcentage - ce qu'il ne peut pas obtenir - mais à la place, il le divise par un nombre maximal d'octets bruts par division. Et de toute façon, une grande partie de votre problème est que vous ne pouvez pas obtenir une taille fiable sur votre archive car elle est trop grande - quoi que vous fassiez, ne recommencez pas - faites des divisions de moins de 4 Go par morceau ce tour , peut être. Ce petit script, au moins, vous permet de le faire sans avoir à écrire un octet non compressé sur le disque.

Voici une version plus courte, dépouillée de l'essentiel - elle n'ajoute pas tous les éléments du rapport:

(       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

Il fait toutes les mêmes choses que le premier, surtout, il n'a tout simplement pas grand-chose à dire à ce sujet. De plus, il y a moins d'encombrement, il est donc plus facile de voir ce qui se passe, peut-être.

Le IFS=problème est simplement de gérer une readligne par itération. Nous readun parce que nous avons besoin que notre boucle se termine lorsque l'entrée se termine. Cela dépend de la taille de votre enregistrement - qui, selon votre exemple, est de 354 octets par. J'ai créé une gziparchive 4 + gb avec des données aléatoires afin de la tester.

Les données aléatoires ont été obtenues de cette façon:

(       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

... mais peut-être que vous n'avez pas à vous en préoccuper autant, car vous avez déjà les données et tout. Retour à la solution ...

Fondamentalement pigz- qui semble décompresser un peu plus vite que le fait zcat- dirige le flux non compressé et les ddtampons qui sortent en blocs d'écriture dimensionnés spécifiquement à un multiple de 354 octets. La boucle readune $linefois chaque itération de test d' entrée est encore arriver, qu'elle printfensuite printfau lz4devant un autre ddest appelé pour lire des blocs de taille spécifiquement à un multiple de 354 octets - pour synchroniser avec le tampon ddprocédé - pour la durée. Il y aura une courte lecture par itération en raison de l'initiale read $line- mais cela n'a pas d'importance, parce que nous l'imprimons dans lz4- notre processus de collecte - de toute façon.

Je l'ai configuré de sorte que chaque itération lira environ 1 Go de données non compressées et les compressera en flux à environ 650 Mo environ. lz4est beaucoup plus rapide que n'importe quelle autre méthode de compression utile - c'est la raison pour laquelle je l'ai choisie ici parce que je n'aime pas attendre. xzferait un bien meilleur travail à la compression réelle, probablement, cependant. Une chose lz4, cependant, est qu'il peut souvent décompresser à des vitesses proches de la RAM - ce qui signifie que vous pouvez décompresser une lz4archive très rapidement, car vous pourriez de toute façon l'écrire en mémoire.

Le grand fait quelques rapports par itération. Les deux boucles imprimeront ddle rapport sur le nombre d'octets bruts transférés et la vitesse et ainsi de suite. La grande boucle affichera également les 4 dernières lignes d'entrée par cycle, et un nombre d'octets pour celui-ci, suivi d'un lsrépertoire dans lequel j'écris les lz4archives. Voici quelques tours de sortie:

/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
la source
gzip -lne fonctionne que pour les fichiers <2GiB non compressés IIRC (quelque chose de plus petit que le fichier OP de toute façon).
Stéphane Chazelas
@ StéphaneChazelas - putain. C'est la seule façon pour moi de trouver une taille non compressée. Sans cela, cela ne fonctionne pas du tout.
mikeserv
4

Le fractionnement des fichiers sur les limites des enregistrements est en fait très facile, sans aucun code:

zcat your_file.gz | split -l 10000 - output_name_

Cela créera des fichiers de sortie de 10000 lignes chacun, avec les noms nom_sortie_aa, nom_sortie_ab, nom_sortie_ac, ... Avec une entrée aussi grande que la vôtre, cela vous donnera beaucoup de fichiers de sortie. Remplacez-le 10000par un multiple de quatre et vous pouvez rendre les fichiers de sortie aussi grands ou petits que vous le souhaitez. Malheureusement, comme avec les autres réponses, il n'y a pas de bon moyen de garantir que vous obtiendrez le nombre souhaité de taille (approximativement) égale de fichiers de sortie sans faire quelques suppositions sur l'entrée. (Ou, en fait, wcvous faites passer le tout .) Si vos enregistrements sont de taille à peu près égale (ou au moins, distribués de manière à peu près égale), vous pouvez essayer de trouver une estimation comme celle-ci:

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

Cela vous indiquera la taille compressée des 1000 premiers enregistrements de votre fichier. Sur cette base, vous pouvez probablement trouver une estimation du nombre de lignes que vous souhaitez dans chaque fichier pour aboutir à quatre fichiers. (Si vous ne voulez pas qu'il reste un cinquième fichier dégénéré, assurez-vous de remplir un peu votre estimation ou préparez-vous à coller le cinquième fichier à la fin du quatrième.)

Modifier: Voici une autre astuce, en supposant que vous souhaitez des fichiers de sortie compressés:

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

Cela créera beaucoup de fichiers plus petits, puis les regroupera rapidement. (Vous devrez peut-être modifier le paramètre -l en fonction de la longueur des lignes de vos fichiers.) Il suppose que vous disposez d'une version relativement récente de coreutils GNU (pour split --filter) et d'environ 130% de la taille de votre fichier d'entrée dans Espace disque libre. Remplacez gzip / zcat par pigz / unpigz si vous ne les avez pas. J'ai entendu dire que certaines bibliothèques de logiciels (Java?) Ne peuvent pas gérer les fichiers gzip concaténés de cette façon, mais je n'ai eu aucun problème jusqu'à présent. (pigz utilise la même astuce pour paralléliser la compression.)

A dessiné
la source
Si vous avez installé pigz, vous pouvez accélérer un peu les choses en remplaçant «pigz -cd» par «zcat».
Drew
2
Ah, je viens de remarquer que vous avez déjà mentionné la scission dans la question. Mais vraiment, à peu près n'importe quelle solution va faire la même chose que diviser sous le capot. La partie difficile consiste à déterminer le nombre de lignes que vous devez mettre dans chaque fichier.
Drew
3

D'après ce que j'ai rassemblé après avoir vérifié la google-sphère et testé un .gzfichier de 7,8 Gio , il semble que les métadonnées de la taille du fichier non compressé d'origine ne soient pas précises (c'est-à-dire incorrectes ) pour les gros .gzfichiers (supérieurs à 4 Gio (peut-être 2 Gio pour certains). versions gzip).
Re mon de test des métadonnées 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

Il semble donc qu'il ne soit pas possible de détruire la taille non compressée sans réellement la décompresser (ce qui est pour le moins un peu rude!)

Quoi qu'il en soit, voici un moyen de diviser un fichier non compressé aux limites des enregistrements, où chaque enregistrement contient 4 lignes .

Il utilise la taille du fichier en octets (via stat) et en awkcomptant les octets (pas les caractères). Que la fin de ligne soit ou non LF| CR| CRLF, ce script gère la longueur de fin de ligne via une variable intégrée 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

Voici le test que j'ai utilisé pour vérifier que le nombre de lignes de chaque fichier est 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

Sortie de test:

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 a été généré par:

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

Ce n'est pas censé être une réponse sérieuse! Je viens de jouer avec flexet cela ne fonctionnera probablement pas sur un fichier d'entrée avec ~ 50 Go (le cas échéant, sur des données d'entrée plus grandes que mon fichier de test):

Cela fonctionne pour moi sur un fichier ~ 1 Go input.txt :

Étant donné le flexfichier d'entrée 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;
    }
}

générer lex.yy.c et le compiler en splitterbinaire avec:

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

Usage:

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

Temps d'exécution pour 1 Go d'entrée.txt :

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

real    2m43.640s
user    0m48.100s
sys     0m1.084s
FloHimself
la source
La lexing réelle ici est si simple que vous ne bénéficiez vraiment pas de lex. Il suffit d'appeler getc(stream)et d'appliquer une logique simple. Savez-vous également que le. (point) le caractère regex dans (f) lex correspond à n'importe quel caractère sauf le saut de ligne , non? Alors que ces enregistrements sont multilignes.
Kaz
@Kaz Bien que vos déclarations soient généralement correctes, cela fonctionne en fait avec les données fournies dans Q.
FloHimself
Seulement accidentellement, car il y a une règle par défaut quand rien ne correspond: consommer un caractère et l'imprimer dans la sortie! Dans d'autres mots, vous pouvez effectuer votre changement de fichier simplement avec une règle qui reconnaît le @caractère, puis laisser la règle par défaut copier les données. Vous avez maintenant votre règle qui copie une partie des données comme un gros jeton, puis la règle par défaut qui obtient la deuxième ligne un caractère à la fois.
Kaz
Merci de clarifier. Je me demande comment résoudriez-vous cette tâche txr.
FloHimself
Je ne suis pas sûr que je le ferais parce que la tâche consiste à faire une chose très simple avec une grande quantité de données, aussi rapidement que possible.
Kaz
1

Voici une solution en Python qui fait passer un fichier d'entrée en écrivant les fichiers de sortie au fur et à mesure.

Une caractéristique de l'utilisation wc -lest que vous supposez que chacun des enregistrements est de la même taille. C'est peut-être vrai ici, mais la solution ci-dessous fonctionne même lorsque ce n'est pas le cas. Il s'agit essentiellement de l'utilisation wc -cou du nombre d'octets dans le fichier. En Python, cela se fait via os.stat ()

Voici donc comment fonctionne le programme. Nous calculons d'abord les points de partage idéaux sous forme de décalages d'octets. Ensuite, vous lisez les lignes du fichier d'entrée en écrivant dans le fichier de sortie approprié. Lorsque vous voyez que vous avez dépassé le prochain point de partage optimal et que vous êtes à une limite d'enregistrement, fermez le dernier fichier de sortie et ouvrez le suivant.

Le programme est optimal dans ce sens, il lit une fois les octets du fichier d'entrée; Obtenir la taille du fichier ne nécessite pas de lire les données du fichier. Le stockage nécessaire est proportionnel à la taille d'une ligne. Mais Python ou le système ont probablement des tampons de fichiers raisonnables pour accélérer les E / S.

J'ai ajouté des paramètres pour le nombre de fichiers à diviser et la taille d'enregistrement au cas où vous souhaiteriez ajuster cela à l'avenir.

Et clairement, cela pourrait également être traduit dans d'autres langages de programmation.

Une autre chose, je ne sais pas si Windows avec son crlf gère correctement la longueur de la ligne comme il le fait sur les systèmes Unix-y. Si len () est désactivé par un ici, j'espère qu'il est évident de savoir comment ajuster le programme.

#!/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))
rocheux
la source
Il ne s'agit pas de fractionner à une limite record. par exemple. Le premier fractionnement de sous-fichiers se produit après la 3ème ligne avec cette entréeprintf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O
1

L'utilisateur FloHimself semblait curieux d'une solution TXR . En voici un qui utilise le TXR Lisp intégré :

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

Remarques:

  1. Pour la même raison pop, il est important de taper chaque tuple de la liste paresseuse de tuples pour que la liste paresseuse soit consommée. Nous ne devons pas conserver une référence au début de cette liste, car la mémoire augmentera alors que nous parcourons le fichier.

  2. (seek-stream fo 0 :from-current)est un cas sans opération seek-stream, qui se rend utile en retournant la position actuelle.

  3. Performance: ne le mentionnez pas. Utilisable, mais n'apportera aucun trophée à la maison.

  4. Étant donné que nous ne vérifions la taille que tous les 1000 tuples, nous pourrions simplement créer une ligne de 4000 lignes.

Kaz
la source
0

Si vous n'avez pas besoin que les nouveaux fichiers soient des morceaux contigus du fichier d'origine, vous pouvez le faire entièrement de sedla manière suivante:

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

L' -nempêche d'imprimer chaque ligne, et chacun des -escripts fait essentiellement la même chose. 1~16correspond à la première ligne et toutes les 16 lignes après. ,+3signifie faire correspondre les trois lignes suivantes après chacune d'elles. w1.txtdit d'écrire toutes ces lignes dans le fichier 1.txt. Cela prend chaque 4ème groupe de 4 lignes et l'écrit dans un fichier, en commençant par le premier groupe de 4 lignes. Les trois autres commandes font la même chose, mais elles sont chacune décalées vers l'avant de 4 lignes et écrivent dans un fichier différent.

Cela se cassera horriblement si le fichier ne correspond pas exactement aux spécifications que vous avez définies, mais sinon cela devrait fonctionner comme vous le vouliez. Je ne l'ai pas profilé, donc je ne sais pas à quel point ce sera efficace, mais il sedest raisonnablement efficace pour l'édition de flux.

Erik
la source