
Le 02/09/08 à 17:38, Stephane Jourdois <kwisatz-shell@rubis.org> a écrit :
On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
Bonjour,
Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des retours chariot pour la lisibilité).
Je répond un peu à l'arrache parce que je n'ai pas le temps de te faire une réponse complète mais voilà quelques idées :
Pour une réponse à l'arache, je la trouve déjà très complète ;-)
1) première règle quand tu fais du parsage, tu t'arranges pour ne parser qu'une seule fois (toi tu parses <nombre de tables>+1 fois, c'est ultra dégueulasse, et évidemment les perfs s'en ressentent (sans parler de la scalabilité en O(n) lors que tu pourrais faire du O(1)).
Oui, évidemment, c'est relativement crade, mais mon but était de découper rapidement mon dump en fichiers plus petits. Je ne m'attendais pas à un truc super rapide mais j'étais quand même étonné que cela n'aille pas plus vite. Le 1er parsing de awk (pour trouver les n° de lignes où couper) est "relativement" rapide, en tout cas bien suffisant pour cette utilisation (2-3s), mais ensuite je pensais que sed irait plus vite lors de chaque sed -ne '22,50 p; 51 q;' < "test.sql" > prefix_access.sql sed -ne '51,704 p; 705 q;' < "test.sql" > prefix_accesslog.sql Même avec les n° de ligne, il parse le début du fichier, mais ensuite il s'arrête à la dernière ligne voulue (et ne parse donc pas la fin). Donc normalement, sed parse le début du fichier 100x.
Le corollaire est la 2ème règle, qui est encore plus importante :
2) deuxième règle du parsage, tu parses en temps réel dès que c'est possible, i.e. tu fais comme si tu parsais un stream, et tu fournis les réponses asap.
Sur le principe je comprend, mais je n'ai pas eu d'idée lumineuse pour l'appliquer à ce cas (je ne modifie pas les lignes mais les écrit dans un fichier différent suivant leur position). sed peut écrire dans un fichier (avec w) mais je ne vois pas comment rendre le nom du fichier variable (même en utilisant le holdspace), et il ne me semble pas que awk puisse écrire dans un fichier variable.
Je m'explique :
- gcc prend un fichier, le parse, puis fait sa sauce. Il met tout le fichier en ram, puis commence à travailler, puis une fois qu'il a fini il sort ce qu'il veut. Un peu comme tu as fait, sauf que toi tu as fait un for (i=0; i < 100; i++) autour de l'appel :-)
- sort parse un stream, mais il ne commence à printer qu'une fois qu'il a tout le stream en ram. évidemment c'est mieux mais toujours pas top.
- cat, lui, il parse un stream et en plus il affiche au fûr et à mesure (modulo le cache éventuel sur stdout), c'est le must du parsage. En général, les scripts sed/awk fonctionnent comme ça (par ligne, donc).
Donc pour finir, la bonne solution : tu écris un bête script (en sed, awk, perl ou ce que tu veux) qui lit une ligne, réalise un traitement sur cette ligne (en particulier un traitement dépendant de l'état, et un traitement qui peut changer l'état), puis tu réitère jusqu'à la fin. Pas de stockage nulle part, et un seul parsage.
Ça m'a donné une idée avec un seul passage sed qui insère des commandes cat dans le dump, qu'on exécute ensuite (sur 1 ligne) : sed -e '/^--/ d; /^\/\*!/d; /^$/d; s/`//g; s/^DROP TABLE IF EXISTS \([^;]*\)/marqueur-de-fin-de-split\ncat > \1.sql <<marqueur-de-fin-de-split\nDROP TABLE IF EXISTS \1/;' < dump.sql |sh (il y a juste un marqueur en trop en 1re ligne et un qui manque en dernière ligne, mais ça marche) Ça va un peu plus vite que mon truc bourrin de l'autre jour, mais pas tant que ça : sur un dump de 100 tables et 22Mo, sed + sh prend 21s (6s + 15s) et la solution awk qui génère des commandes sed + éxécution par le shell 26s.
Pour ton cas, et en perl, ça donne (écrit dans le mail donc non testé, désolé :-)) :
C'est déjà sympatique de donner du code commenté ! Vu ma connaissance symbolique de perl, j'avais éliminé d'office cette solution ;-)
#!/usr/bin/perl use strict; use warnings;
my $db = undef; # Current database name my $table = undef; # Current table name my $fh = undef; # Current filehandle while (<>) { if (/^CREATE DATABASE .* `([^`]+)`/) { $db = $1; } elsif (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1 close $fh if $fh; # pas nécessaire vu que le open # suivant ferme automatiquement # le filehandle. open $fh, '>', "$db.$table.sql"; }
if ($db and $table) { print $fh $_ if $fh; } } close $fh; -----
Je ne suis pas sûr que ça donne strictement le même résultat que ton script vu sa complexité, mais bon ça me semble être un début pour toi.
Ton script met 0.351s ;-) Bon ben, il me reste à apprendre perl si je veux jouer dans la même cour... Daniel PS : Le script modifié utilisé (j'ai pas de CREATE DATABASE dans mes dump donc j'ai viré les ref à $db, ajouté un ; manquant et viré la ligne close qui lui plaisait pas) ----- #!/usr/bin/perl use strict; use warnings; my $table = undef; # Current table name my $fh = undef; # Current filehandle while (<>) { if (/^DROP TABLE IF EXISTS `([^`]+)`/) { $table = $1; open $fh, '>', "perl_$table.sql"; } if ($table) { print $fh $_ if $fh; } } close $fh; -----