Content-type: text/html
Il y a un nombre extraordinaire de personnes qui semblent ne rien connaître de l'utilisation de débogueur de Perl, bien qu'ils utilisent ce language quotidiennement. Ceci est pour eux.
#!/usr/bin/perl
$var1 = 'Salut le monde'; # toujours voulu faire cela :-) $var2 = "$varl\n";
print $var2; exit;
Bien que ce script se compile et s'exécute gaiement, il ne va probablement pas faire ce que l'on en attend, c'est à dire qu'il ne va pas du tout écrire ``Salut le monde\n''; il va en revanche faire exactement ce que nous lui avons demandé de faire, les ordinateurs ayant légèrement tendance à agir de cette manière. Ce script va donc écrire un caractère de nouvelle ligne, et vous allez obtenir ce qui ressemble à une ligne blanche. Il semble que le script ne contiennent que deux variables, alors qu'il en contient en fait trois (à cause de la faute de frappe).
$var1 = 'Hello World' $varl = undef $var2 = "\n"
Afin de mettre en évidence ce type de problème, nous pouvons forcer la déclaration de chaque variable avant son utilisation en faisant appel au module « strict » par l'insertion après la première ligne du script de l'instruction 'use strict'. Maintenant, lors de l'exécution, perl se plaint de trois variables non déclarées, et nous obtenons quatre messages d'erreur car une variable est référencée deux fois :
Global symbol "$var1" requires explicit package name at ./t1 line 4. Global symbol "$var2" requires explicit package name at ./t1 line 5. Global symbol "$varl" requires explicit package name at ./t1 line 5. Global symbol "$var2" requires explicit package name at ./t1 line 7. Execution of ./hello aborted due to compilation errors.
Merveilleux ! Et pour supprimer ces erreurs, nous déclarons explicitement toutes les variables. Maintenant notre script ressemble à ceci :
#!/usr/bin/perl use strict;
my $var1 = 'Salut le monde'; my $varl = ''; my $var2 = "$varl\n";
print $var2; exit;
Nous procédons alors à une vérification de la syntaxe (toujours une bonne idée) avant d'exécuter à nouveau le script :
> perl -c hello hello syntax OK
Et maintenant quand nous exécutons ce script, nous obtenons toujours ``\n'', mais au moins nous savons pourquoi. La compilation du script a révélé la variable '$varl' (avec la lettre 'l') et le simple changement de $varl par $var1 résoud le problème.
#!/usr/bin/perl use strict;
my $key = 'welcome'; my %data = ( 'this' => qw(that), 'tom' => qw(and jerry), 'welcome' => q(Hello World), 'zip' => q(welcome), ); my @data = keys %data;
print "$data{$key}\n"; exit;
Tout semble correct. Après avoir été testé avec la vérification de syntaxe (perl -c nom_du_script) nous exécutons ce script et tout ce que nous obtenons est à nouveau une ligne blanche ! Hmmmmm. Dans ce cas, une approche courante du débogage serait de parsemer délibérement quelques instructions print, pour ajouter une première vérification juste avant d'écrire nos données, et une seconde juste après :
print "All OK\n" if grep($key, keys %data); print "$data{$key}\n"; print "done: '$data{$key}'\n";
Après une nouvelle tentative :
> perl data All OK
done: ''
Après s'être usé les yeux sur ce code pendant un bon moment, nous allons boire une tasse de café et nous tentons une nouvelle approche. C'est-à-dire que nous appelons la cavalerie en invoquant perl avec l'option '-d' sur la ligne de commande.
> perl -d data Default die handler restored.
Loading DB routines from perl5db.pl version 1.07 Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(./data:4): my $key = 'welcome';
Donc, ce que nous venons de faire ici est de lancer notre script avec le débogueur intégré de perl. Celui-ci s'est arrété à la première ligne de code exécutable et attend notre action.
Avant de poursuivre plus avant, vous allez souhaiter connaître comment quitter le débugueur : tapez simplement la lettre 'q', pas les mots 'quit', ni 'exit'.
DB<1> q >
C'est cela, vous êtes à nouveau au paddock.
DB<1> h h List/search source lines: Control script execution: l [ln|sub] List source code T Stack trace - or . List previous/current line s [expr] Single step [in expr] w [line] List around line n [expr] Next, steps over subs f filename View source in file Repeat last n or s /pattern/ ?patt? Search forw/backw r Return from subroutine v Show versions of modules c [ln|sub] Continue until position Debugger controls: L List break/watch/actions O [...] Set debugger options t [expr] Toggle trace [trace expr] <[<]|{[{]|>[>] [cmd] Do pre/post-prompt b [ln|event|sub] [cnd] Set breakpoint ! [N|pat] Redo a previous command d [ln] or D Delete a/all breakpoints H [-num] Display last num commands a [ln] cmd Do cmd before line = [a val] Define/list an alias W expr Add a watch expression h [db_cmd] Get help on command A or W Delete all actions/watch |[|]db_cmd Send output to pager ![!] syscmd Run cmd in a subprocess q or ^D Quit R Attempt a restart Data Examination: expr Execute perl code, also see: s,n,t expr x|m expr Evals expr in list context, dumps the result or lists methods. p expr Print expression (uses script's current package). S [[!]pat] List subroutine names [not] matching pattern V [Pk [Vars]] List Variables in Package. Vars can be ~pattern or !pattern. X [Vars] Same as "V current_package [Vars]". For more help, type h cmd_letter, or run man perldebug for all docs.
Plus d'options que vous ne le pensiez ! Ce n'est pas si terrible qu'il y parait, et il est très utile et en plus amusant, d'en connaître plus à propos de chacune de ces commandes.
Il y a quelques options utiles à connaître immédiatement. Vous ne pensez probablement pas que vous utilisez des bibliothèques en ce moment, mais 'v' va vous montrer quels modules sont actuellement actifs, appelés par le débogueur aussi bien que par le script. 'V' et 'X' montrent les variables du programme rangées par package et peuvent être restreintes par l'utilisation de motifs.'m' montre les méthodes et 'S' montre tous les sous-programmes (par motifs).
DB<2>S str dumpvar::stringify strict::bits strict::import strict::unimport
L'utilisation de 'X' et de ses cousins demande de ne pas utiliser l'identificateur de type ($@%), seulement le 'nom' :
DM<3>X ~err FileHandle(stderr) => fileno(2)
Rappelez-vous que nous sommes dans notre minuscule programme avec un problème, nous devrions jeter un oeil pour voir où nous sommes et à quoi nos données ressemblent. Avant tout, ouvrons une fenêtre sur notre position actuelle (la première ligne de code dans notre cas), grâce à la lettre 'w' :
DB<4> w 1 #!/usr/bin/perl 2: use strict; 3 4==> my $key = 'welcome'; 5: my %data = ( 6 'this' => qw(that), 7 'tom' => qw(and jerry), 8 'welcome' => q(Hello World), 9 'zip' => q(welcome), 10 );
À la ligne 4 se présente un pointeur utile, qui vous indique où vous vous situez actuellement. Pour voir plus avant dans le programme, tapez encore 'w'.
DB<4> w 8 'welcome' => q(Hello World), 9 'zip' => q(welcome), 10 ); 11: my @data = keys %data; 12: print "All OK\n" if grep($key, keys %data); 13: print "$data{$key}\n"; 14: print "done: '$data{$key}'\n"; 15: exit;
Et si vous voulez lister à nouveau la ligne 5, tapez 'l 5', (remarquez l'espace) :
DB<4> l 5 5: my %data = (
Dans le cas de ce script, il n'y pas grand chose à voir, mais il peut y avoir des pages entières à explorer et 'l' peut être très utile. Pour réinitialiser votre vue sur la ligne qui va s'exécuter, tapez un simple point '.' :
DB<5> . main::(./data_a:4): my $key = 'welcome';
La ligne affichée est la prochaine qui sera exécutée, sans que cette exécution ne se soit produite pour l'instant. Donc bien que nous puissions imprimer une variable grâce à la commande 'p', tout ce que nous allons obtenir est une valeur vide (non définie). Ce que nous devons faire est exécuter la prochaine instruction exécutable en tapant 's' :
DB<6> s main::(./data_a:5): my %data = ( main::(./data_a:6): 'this' => qw(that), main::(./data_a:7): 'tom' => qw(and jerry), main::(./data_a:8): 'welcome' => q(Hello World), main::(./data_a:9): 'zip' => q(welcome), main::(./data_a:10): );
Maintenant, nous pouvons observer cette première variable ($clef)
DB<7> p $key welcome
La ligne 13 est celle où l'action a lieu, donc continuons notre progression grâce à la lettre 'c', qui insert un point d'arrêt 'à usage unique' à la ligne ou au sous-programme indiqué.
DB<8> c 13 All OK main::(./data_a:13): print "$data{$key}\n";
Nous avons dépassé notre vérification (où 'All OK' était imprimé) et nous nous sommes arrêtés juste avant le gros du travail. Nous pourrions essayer de lister quelques variables pour voir ce qu'il se passe.
DB<9> p $data{$key}
Pas grand chose ici, observons notre hachage :
DB<10> p %data Hello Worldziptomandwelcomejerrywelcomethisthat
DB<11> p keys %data Hello Worldtomwelcomejerrythis
Bon, ceci n'est pas très facile à lire, et dans l'écran d'aide (h h), la commande 'x' semble prometteuse :
DB<12> x %data 0 'Hello World' 1 'zip' 2 'tom' 3 'and' 4 'welcome' 5 undef 6 'jerry' 7 'welcome' 8 'this' 9 'that'
Ceci n'aide pas beaucoup, deux 'bienvenue', mais sans que l'on sache s'ils correspondent à des clefs ou à des valeurs. Il s'agit juste de l'affichage d'une image d'un tableau, dans ce cas sans grande utilité. L'astuce est ici d'utiliser une référence à la structure de données :
DB<13> x \%data 0 HASH(0x8194bc4) 'Hello World' => 'zip' 'jerry' => 'welcome' 'this' => 'that' 'tom' => 'and' 'welcome' => undef
La référence est réellement détaillée, et nous mettons finalement en évidence notre problème. Notre citation est parfaitement valide, mais erronée pour notre propos, avec 'and jerry' traité comme deux mots séparés plutôt que comme une phrase, donc entraînant un mauvais alignement des paires du hachage.
Le commutateur '-w' nous aurait indiqué ce problème si nous l'avions utilisé au départ, et nous aurait évité pas mal de soucis :
> perl -w data Odd number of elements in hash assignment at ./data line 5.
Nous réparons notre citation : 'tom' =>q(et jerry), et exécutons à nouveau notre programme. Nous obtenons alors le résultat attendu :
> perl -w data Bonjour le monde
Pendant que nous y sommes, regardons de plus près la commande 'x'. Elle est réellement utile et vous affichera merveilleusement le contenu de reférences imbriquées, d'objet complets, de fragments d'objets, de tout ce que vous voudrez bien lui appliquer :
Construisons rapidement un objet, et e-x-plorons le. D'abord, démarrons le débogueur : il demande une entrée à partir de STDIN, donnons-lui donc quelque chose qui n'engage à rien, un zéro :
> perl -de 0 Default die handler restored.
Loading DB routines from perl5db.pl version 1.07 Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(-e:1): 0
Maintenant construisez au vol un objet sur quelques lignes (notez l'antislash) :
DB<1> $obj = bless({'unique_id'=>'123', 'attr'=> \ cont: {'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class')
Et jetez un œil dessus :
DB<2> x $obj 0 MY_class=HASH(0x828ad98) 'attr' => HASH(0x828ad68) 'col' => 'black' 'things' => ARRAY(0x828abb8) 0 'this' 1 'that' 2 'etc' 'unique_id' => 123 DB<3>
Utile, n'est-ce pas ? Vous pouvez utiliser eval sur n'importe quoi, et expérimenter avec des fragments de programme ou d'expressions régulières jusqu'à ce que les vaches rentrent à l'étable :
DB<3> @data = qw(this that the other atheism leather theory scythe)
DB<4> p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data)) atheism leather other scythe the theory saw -> 6
Si vous voulez voir l'historique des commandes, tapez un 'H' :
DB<5> H 4: p 'saw -> '.($cnt += map { print "\t:\t$_\n" } grep(/the/, sort @data)) 3: @data = qw(this that the other atheism leather theory scythe) 2: x $obj 1: $obj = bless({'unique_id'=>'123', 'attr'=> {'col' => 'black', 'things' => [qw(this that etc)]}}, 'MY_class') DB<5>
Et si vous voulez répéter une quelques commande précédente, utilisez le point d'exclamation :'!' :
DB<5> !4 p 'saw -> '.($cnt += map { print "$_\n" } grep(/the/, sort @data)) atheism leather other scythe the theory saw -> 12
Pour plus d'informations sur les références, voyez les pages de manuel perlref et perlreftut.
#!/usr/bin/perl -w use strict;
my $arg = $ARGV[0] || '-c20';
if ($arg =~ /^\-(c|f)((\-|\+)*\d+(\.\d+)*)$/) { my ($deg, $num) = ($1, $2); my ($in, $out) = ($num, $num); if ($deg eq 'c') { $deg = 'f'; $out = &c2f($num); } else { $deg = 'c'; $out = &f2c($num); } $out = sprintf('%0.2f', $out); $out =~ s/^((\-|\+)*\d+)\.0+$/$1/; print "$out $deg\n"; } else { print "Usage: $0 -[c|f] num\n"; } exit;
sub f2c { my $f = shift; my $c = 5 * $f - 32 / 9; return $c; }
sub c2f { my $c = shift; my $f = 9 * $c / 5 + 32; return $f; }
Pour une raison inconnue, la conversion des Fahrenheit en Celsius ne donne pas le résultat escompté. Voici ce que ce programme effectue :
> temp -c0.72 33.30 f
> temp -f33.3 162.94 c
Pas très cohérent ! Nous allons placer manuellement un point d'arrêt dans le programme et exécuter celui-ci dans le débogueur pour voir ce qui se passe. Un point d'arrêt est un drapeau. Le débogueur va exécuter le programme sans interruption jusqu'à ce drapeau. Quand il l'atteint, il arrête l'exécution et présente une invite pour la suite de l'interaction. En utilisation normale, ces commandes de débogage sont totalement ignorées, et elle peuvent être conservées en toute sécurité dans le programme final, bien quelles en augmentent quelque peu le désordre.
my ($in, $out) = ($num, $num); $DB::single=2; # insert at line 9! if ($deg eq 'c') ...
> perl -d temp -f33.3 Default die handler restored.
Loading DB routines from perl5db.pl version 1.07 Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(temp:4): my $arg = $ARGV[0] || '-c20';
Nous allons simplement descendre jusqu'à notre point d'arrêt prédéfini grâce à la commande 'c' :
DB<1> c main::(temp:10): if ($deg eq 'c') {
suivie par une commande 'w' pour voir où nous sommes :
DB<1> w 7: my ($deg, $num) = ($1, $2); 8: my ($in, $out) = ($num, $num); 9: $DB::single=2; 10==> if ($deg eq 'c') { 11: $deg = 'f'; 12: $out = &c2f($num); 13 } else { 14: $deg = 'c'; 15: $out = &f2c($num); 16 }
et par une commande 'p' pour montrer le contenu des variables que nous utilisons :
DB<1> p $deg, $num f33.3
Nous pouvons placer un autre point d'arrêt à n'importe quelle ligne commencant par un numéro suivi par deux points. Nous utiliserons la ligne 17, cette ligne étant la première après la sortie du sous-programme f2c. Nous prévoyons de faire une pause à cette endroit plus tard.
DB<2> b 17
Il n'y a pas d'indications en retour, mais vous pouvez voir les points d'arrêt placés en utilisant la commande 'L'.
DB<3> L temp: 17: print "$out $deg\n"; break if (1)
Il est à noter que les points d'arrêts peuvent être supprimés en utilisant les commandes 'd' ou 'D'.
Nous allons alors continuer plus avant dans notre sous-programme, cette fois en utilisant, à la place du numéro de ligne, le nom du sous-programme suivi par la commande 'w', maintenant familière,
DB<3> c f2c main::f2c(temp:30): my $f = shift;
DB<4> w 24: exit; 25 26 sub f2c { 27==> my $f = shift; 28: my $c = 5 * $f - 32 / 9; 29: return $c; 30 } 31 32 sub c2f { 33: my $c = shift;
Notez que si il existait un appel à un sous-programme entre notre position et la ligne 29, et si nous voulions traverser ce sous-programme pas-à-pas, nous pourrions utiliser la commande 's'. Pour le franchir, il faudrait utiliser 'n' qui exécuterait alors le sous-programme sans l'inspecter. Dans notre cas, nous continuons simplement jusqu'à la ligne 29.
Regardons la valeur retournée :
DB<5> p $c 162.944444444444
Ce n'est pas du tout la réponse exacte, mais l'opération semble correcte. Je me demande si le problème ne serait pas lié aux priorités des opérateurs ? Essayons notre opération avec quelques autres combinaisons de parenthèses.
DB<6> p (5 * $f - 32 / 9) 162.944444444444
DB<7> p 5 * $f - (32 / 9) 162.944444444444
DB<8> p (5 * $f) - 32 / 9 162.944444444444
DB<9> p 5 * ($f - 32) / 9 0.722222222222221
:-) Ce dernier essai ressemble plus à ce que l'on souhaite ! Bien, maintenant nous pouvons définir la variable à renvoyer, et nous allons sortir de notre sous-programme grâce à la commande 'r' :
DB<10> $c = 5 * ($f - 32) / 9
DB<11> r scalar context return from main::f2c: 0.722222222222221
Semble correct, continuons donc jusqu'à la fin du script :
DB<12> c 0.72 c Debugged program terminated. Use q to quit or R to restart, use O inhibit_exit to avoid stopping after program termination, h q, h R or h O to get additional info.
Une correction rapide de la ligne erronée (insertion des parenthèses manquantes) dans le programme et nous avons terminé.
a
W
t
> perl -Dr -e '/^pe(a)*rl$/i' Compiling REx `^pe(a)*rl$' size 17 first at 2 rarest char at 0 1: BOL(2) 2: EXACTF (4) 4: CURLYN[1] {0,32767}(14) 6: NOTHING(8) 8: EXACTF (0) 12: WHILEM(0) 13: NOTHING(14) 14: EXACTF (16) 16: EOL(17) 17: END(0) floating `'$ at 4..2147483647 (checking floating) stclass `EXACTF ' anchored(BOL) minlen 4 Omitting $` $& $' support.
EXECUTING...
Freeing REx: `^pe(a)*rl$'
Vouliez-vous vraiment savoir ?:-) Pour plus de détails sanglants sur la mise en oeuvre des expressions régulières, regardez les pages de manuel perlre, perlretut, et pour décoder les étiquettes mystérieuses (voir ci-dessus BOL, CURLYN etc..), se réferer au manuel perldebguts.
$|=1;
Pour voir l'extrémité d'un fichier journal en croissance dynamique, tapez à partir de la ligne de commande :
tail -f $error_log
Envelopper tous les appels à 'die' dans une routine peut être utilisé pour voir comment et à partir de quoi ils sont appelés. La page de manuel perlvar présente plus d'informations :
BEGIN { $SIG{__DIE__} = sub { require Carp; Carp::confess(@_) } }
Diverses techniques utilisées pour la redirection des descripteurs STDOUT et STDERR sont expliquées dans les pages de manuel perlopentut et perlfaq8.
> perl -d my_cgi.pl -nodebug
Bien sûr les pages de manuel CGI et perlfaq9 vous en dirons plus.
Vous n'avez pas à faire tout ceci à partir de la ligne de commande, il existe quelques possibilités d'utilisation d'interfaces graphiques. Ce qui est agréable alors est de de pouvoir agiter la souris au dessus d'une variable et de voir apparaître dans une fenêtre appropriée ou dans un ballon d'aide son contenu. Plus besoin de se fatiguer à taper 'x $nomdevariable' :-)
En particulier, vous pouvez partir à la chasse de :
ptkdb Enveloppe pour le débogueur intégré basée sur perlTK
ddd data display debugger (débogeur exposeur de données)
PerlDevKit et PerlBuilder sont des outils spécifiques à Windows NT.
NB. Plus d'informations sur ces outils ainsi que sur d'autres équivalents seraient appréciées.
Il y a bien sûr beaucoup plus à découvrir à propos du débogueur, ce tutoriel n'ayant fait qu'en explorer la surface. La meilleure façon d'en apprendre plus est d'utiliser perldoc pour découvrir beaucoup d'autres d'aspects du langage, de lire l'aide en ligne (la page de manuel perlbug devrait être votre prochaine lecture) et bien sûr d'expérimenter.
Ronald J Kimball <jk@linguist.dartmouth.edu>
Hugo van der Sanden <hv@crypt0.demon.co.uk>
Peter Scott <Peter@PSDT.com>