Découper une ligne d'un fichier en plusieurs lignes

Bonjour,

j’ai un fichier data.xml de 4 Go dont les données sont sur une seule ligne. Pour simplifier, voici le contenu de mon fichier xml :

<xml version="1.0" encoding="utf-8"?><dossier><fichier>abricot.txt</fichier><fichier>banane.txt</fichier><fichier>clémentine.txt</fichier></dossier></xml>

Si vous voulez voir plus clair :

<xml version="1.0" encoding="utf-8"?>
<dossier>
<fichier>abricot.txt</fichier>
<fichier>banane.txt</fichier>
<fichier>clémentine.txt</fichier>
</dossier>
</xml>

Je rappelle que les données sont sur une seule ligne dans le fichier data.xml. Comme le fichier est volumineux (4Go), je suis obligé de découper ce fichier en plusieurs fichiers pour pouvoir les parser sans saturer ma mémoire ram.
Pour ce faire je recherche une commande permettant de découper le fichier data.xml lorsqu’il trouve à chaque fois la 1000 ème balise fermante . Ainsi on a 1.xml (qui contient les 1000 premières balise ), 2.xml (qui contient les 1000 balises suivantes), 3.xml (qui contient les 1000 balises suivantes), et ainsi de suite.

Une idée ?

C’est vrai que j’aurais pu utiliser sed -i “s/></>\n</g” data.xml | split -d -l 1000, mais comme ma mémoire ram est limité, la partie sed de cette commande est aborté au bout d’un certain temps.

Merci d’avance, cordialement

Intéressant c’est ce que j’allais proposer.

Je pense que perl est ton ami.

[code]#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;

my $filename = shift;

open FILE, ‘<’, $filename or die “$!”;

my $size = 4096;
my $limit = 1000;

my $buff1;
my $buff2 = ‘’;
my $index = 1;
my $count = 0;

open OUT, ‘>’, $index.’.xml’;

while(read FILE, $buff1, $size) {
chomp $buff1;
$buff2 .= $buff1;
while($buff2 =~ /(.?</fichier>)(.)/g) {
++$count;
if($count gt $limit) {
++$index;
close OUT;
open OUT, ‘>’, $index.’.xml’ or die “$!”;
}
say OUT $1;
$buff2 = $2;
}
}
say OUT $buff2;

close OUT;
close FILE;[/code]
Niveau perf ça doit pas être terrible parce qu’on utilise une lecture simple et pas un mmap, mais la consommation mémoire devrait rester correcte (voir très correcte). Faudrait aussi regarder si sed n’a pas une option pour refaire le comportement que j’ai écris en sed./

bonjour,
pour la mémoire,
le swap a quelle taille SVP?
A+
JB1

[quote=“jb1”]bonjour,
pour la mémoire,
le swap a quelle taille SVP?
A+
JB1[/quote]


root@localhost:/home/guest/# free
             total       used       free     shared    buffers     cached
Mem:       3844084    1397792    2446292          0      33888     472988
-/+ buffers/cache:     890916    2953168
Swap:      8787960      29924    8758036
root@localhost:/home/guest/# free -m
             total       used       free     shared    buffers     cached
Mem:          3753       1364       2389          0         33        460
-/+ buffers/cache:        870       2883
Swap:         8581         29       8552
root@localhost:/home/guest/# free -g
             total       used       free     shared    buffers     cached
Mem:             3          1          2          0          0          0
-/+ buffers/cache:          0          2
Swap:            8          0          8

=> J’ai 4 Go de Ram, et 8G pour la swap

Merci misterFreeze, j’ai essayé ton script, mais ça ne me coupe pas à toutes les 1000 , mais ça coupe à chaque , comme si le paramètre my $limit = 1000 a été zappé durant l’exécution du script.

Je n’ai jamais fait de perl dans ma vie (même si en voyant ton code, ça a l’air un language simpliste, mais de bonne réputation pour les regex).

Ok, je suis idiot (faut dire que j’ai écris mon script rapidement avant une réunion), il faut remettre à 0 le compteur :

[code]#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;

my $filename = shift;

open FILE, ‘<’, $filename or die “$!”;

my $size = 4096;
my $limit = 1000;

my $buff1;
my $buff2 = ‘’;
my $index = 1;
my $count = 0;

open OUT, ‘>’, $index.’.xml’;

while(read FILE, $buff1, $size) {
chomp $buff1;
$buff2 .= $buff1;
while($buff2 =~ /(.?</fichier>)(.)/g) {
++$count;
if($count gt $limit) {
$count = 0;
++$index;
close OUT;
open OUT, ‘>’, $index.’.xml’ or die “$!”;
}
say OUT $1;
$buff2 = $2;
}
}
say OUT $buff2;

close OUT;
close FILE;[/code]

Maintenant que j’ai pris le temps d’ouvrir le man de sed, tu devrais pouvoir avoir un résultat approchant avec :

Les noms de fichiers ne sont pas terribles mais ça doit être plus performant.

[quote=“MisterFreez”]Code:
sed -l4096 ‘s@()@\1\n@g’ fichier.xml | split -dl1[/quote]
=> ça ne marche pas car ma mémoire ram n’est pas assez suffisante pour aller jusqu’au bout.
Je laisse tomber sed, car j’ai l’impression que sed charge d’abord tout le fichier avant le parsage.

Pour le script perl corrigé, toujours le même résultat (pas de découpage à toutes les 1000 ), mais ce que j’aime bien dans ton script, il lit progressivement le fichier (comme le parsage événementiel d’un fichier xml).

bonjour,
avec 8GB de swap cela devrait passer, il y a un pb a condition de travailler sous root (SHMMAX)
et là cela swap!!!

une autre solution plus élègante et en rapport avec l’architecture,

on considère que le fichier d’une ligne passe dans un tuyau
sur ce tuyau on ouvre une fenétre (taille buffer)
on parcoure chaque caractère à la recherche de l’élèment indiquant une fin de phrase
donc écrit la phrase, on rajoute \n dans ce fichier sortie
résumons:
-un fichier en entrée
un fichier en sortie
une fenètre de travail
pour le fun on compte le nombre d’insertion (nombre de ligne)
on inscrit également l’heure de début et de fin de traitement

pour les autres langages jocker!
A+
JB1
:079

mais prérequis il faut parler le C

[quote=“fluo”][quote=“MisterFreez”]Code:
sed -l4096 ‘s@()@\1\n@g’ fichier.xml | split -dl1[/quote]
=> ça ne marche pas car ma mémoire ram n’est pas assez suffisante pour aller jusqu’au bout.
Je laisse tomber sed, car j’ai l’impression que sed charge d’abord tout le fichier avant le parsage.[/quote]
Ça me déçoit de sa part…

Hum, je suis perplexe. Voici un version qui me donnera peut être plus d’infos :

[code]#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;

my $filename = shift;

open FILE, ‘<’, $filename or die “$!”;

my $size = 4096;
my $limit = 1000;

my $buff1;
my $buff2 = ‘’;
my $index = 1;
my $count = 0;

open OUT, ‘>’, $index.’.xml’;

while(read FILE, $buff1, $size) {
chomp $buff1;
$buff2 .= $buff1;
while($buff2 =~ /(.?</fichier>)(.)/g) {
++$count;
if($count gt $limit) {
die “count = $count ; limit = $limit ; index = $index”;
$count = 0;
++$index;
close OUT;
open OUT, ‘>’, $index.’.xml’ or die “$!”;
}
say OUT $1;
$buff2 = $2;
}
}
say OUT $buff2;

close OUT;
close FILE;[/code]

[quote=“jb1”]on considère que le fichier d’une ligne passe dans un tuyau
sur ce tuyau on ouvre une fenétre (taille buffer)
on parcoure chaque caractère à la recherche de l’élément indiquant une fin de phrase
donc écrit la phrase, on rajoute \n dans ce fichier sortie[/quote]
Tu vois une grande différence avec ce que fais mon script perl ?
[ul]
[li]Lire le fichier petit à petit dans un buffer[/li]
[li]découper selon le motif et écrire dans un fichier[/li]
[li]changer de fichier dès qu’on passe une certaine limite[/li][/ul]

Pourquoi ça ? Éventuellement si tu cherche à utiliser des pipes ou mmap ça peut se comprendre mais c’est dispo dans les autres langages aussi.

Merci misterFreeze, ça marche, ça consomme très peu de mémoire.
Mais d’après toi, qui est le plus rapide (je voudrais un classement du plus rapide vers le moins rapide) pour le parsage de gros fichier avec regex en ligne de commande ? : bash, php, perl, python ?

Probablement perl, les autres sont tous au même niveau quant au traitement des expressions régulières, ils utilisent PCRE qui est écrite en C.
La performance intrinsèque des langage (et leur gestion des chaines de caractère par exemple) jour probablement un rôle pus déterminant. perl a simplement l’avantage d’avoir des expressions régulières extrêmement poussées (cf. : articles.mongueurs.net/magazines … ag106.html). Une expression régulière ça s’optimise.

En l’occurrence dans le cas présent je présume que le traitement a du être très long. C’est la lecture du fichier qui cause principalement ce problème. J’ai choisi d’avoir un buffer de 4096 caractères en ayant dans l’idée que c’était de l’ASCII et ainsi lire toujours l’équivalent d’un buffer noyau (une page mémoire soit 4096o), mais c’est loin d’être optimal. Ce qu’il faudrait c’est utiliser Mmap et utiliser une taille de buffer qui correspond à la taille moyenne du contenu de la balise comme ça :
[ul]
[li]tu laisse le noyau faire ça tambouille pour mapper le fichier en mémoire de manière optimisé[/li]
[li]tu limite le nombre de tour de boucle qui contient l’expression régulière[/li][/ul]

Note : je suis pas un expert des expressions régulières, mais je pense qu’il est possible d’optimiser celle que j’ai donné pour supprimer la boucle. Ce serais probablement plus propre et peut être plus rapide.

Merci pour ces explications :slightly_smiling: .

bonsoir MisterFreez et les autres,
mon problème, je ne parle pas “Perl” mais d’autres langages
bref:
pour la mémoire si 8GB de barrettes la valeur de shmmax est à revoir
A+
JB1
8)