___ ___ ____ ___ __ _______ __ __ ______ | | | | | ; | | | |___ | |___ | | | .------. | | .- ---' , '-----'------'-----'--- ----'------- - -- - - - - ' _|________|__ ¦ | mkd.13 Perl : À la conquête du chameau; Part 5 : aka 'Time to really use the force' ' _ ___ __ ____ : | | '-----.------- .-----.------.-----| |----- - ------- - - - ---- - - - ____| |____ |______| ____| |____ | | | | |___ _ ________ ___|___ ____|_________ _______ | Nous voici donc rendu dans les restants et coins lugubres de mon tutorial. En faite j'ai essayé tout au long de ce tuto d'insérer les boutes "freaks" du moment, mais au cours du temps (surtout avant d'écrire le dernier épisode) j'ai ramasser plein d'affaires cool, qui fittait pas vraiment... Dans les affaires cools je parles des regex, qui sont une partie importante à considéré en Perl lorsque vous parsez du texte. En faite, pour tout langage les Regular Expression (regex) sont importantes lorsqu'il s'agit de parser quelque chose. Aussi, ya plein de fonctions en perl qui servent a manipuler des variables, de façons a ce que la logique du programme revienne a autre chose que "J'incrémente mon putain de $i pour avancer dans un tableau". Bon sans plus tarder voici le topo sur les regex, suivit de certaines fonctions cool qui nous rende la vie facile et la compréhension du code impossible par ceux qui les connaissent pas ;-) Regex ¯¯¯¯¯ Pour ceux qui savent pas pentoute c'est quoi les regex, bien pauvre vous autres vous avez une petite longueur d'avance à rattraper.. Je suggère de regarder egrep dans unix, mais attention Perl marche pas exactement comme egrep.. Dans tout les cas je crois que vous allez pouvoir vous débrouiller juste avec le tuto ici.. je vais partir du début. Les regex servent a matcher du texte selon des patterns que vous définissez. En gros l'engine de regex parse le texte pour vous et tante de trouver ce que vous lui avez demandé. Tout plein de caractères spéciaux sont donc utiliser pour faire des contraintes. Donc principalement, ce que vous avez besoin de savoir, c'est de comment utiliser ces caractères spéciaux, et quel texte vous voulez matcher. On va supposé que vous êtes pas trop vedge et que vous pouvez savoir ce que vous voulez chercher. Bon ok; pour les stoned : Chercher le mot "meuh" dans la phrase "allo meuh"... *sigh* Pour les courageux et ceux qui veulent venir les plus leets au monde avec les regex (surtout si vous saviez pas c'était quoi avant la, pis que la c'est rendu votre nouveau trip depuis 2 paragraphes) je vous conseil de lire Mastering Regular Expression dans la collection O'Reilly (les 2 hiboux). Ya pas plus complet que ça, et ça porte pas juste sur le Perl.. mais bien les regex en général, avec bien sur, Perl en premier plan. Bon nous allons donc commencer avec le début du commencement de la chose, savoir si un mot existe dans une phrase, strstr pour les C/PHP freaks. Perl (voulant faire à sa tête comme d'habitude) propose une syntaxe spéciale, qui est toute sauf un call a une fonction : $a =~ /meuh/; Permet de savoir si l'occurrence "meuh" apparaît dans $a, en faite ça retourne de koi qui peut être interprété comme un true/false: if ($a =~ /meuh/) { print "jai trouvé meuh!"; } Simple et pas trop chiant.. il est a noter que les // sont les déléminateurs par défaut, et que on peut les remplacer avec qr// (exemple : $a -~ qr"meuh"). Bon alors jusque la, c'est simple, rapide, terriblement efficace (lalala) mais vu que j'ai pas envie que ça le reste longtemps (sinon on va s'endormir), on va maintenant introduire un concept plus avancer qui va nous aider a bien comprendre le "pourquoi la même chose fait pas la même affaire". Avec les regex en perl, on peut grouper de 2 manières les caractères dans notre search pattern. La search pattern elle même est entre // par défaut, et les groupements sont () pour regrouper des caractère dans un block, et [] pour regrouper le tout de façon à créer un range. o_O me direz vous, on va y aller avec des exemples, histoire de comprendre ça : $a =~ /[A-Z][a-z]/; Cte ligne la va checker si un char entre A et Z suivi d'un char entre a et z (case sensitive hein!) match dans notre $a.. On peut aussi donner une série de char a matcher, genre si on veut trouver un a ou un d ou un e : $a =~ /[ade]/; Et bien sur on peut y aller avec [0-9]. Si on veut trouver le contraire (ce qui n'est pas) on peut faire [^0-9] ce qui va nous sortir true si on match des pas de 0 à 9. Bon la prenez un peu de temps a réfléchir a la chose suivante : la regex retourne un true lorsqu'elle trouve ce que on lui demande. C'est un concept important a comprendre, car c'est comme ça qu'on va pouvoir faire des tests dans des chaînes avancés, faire des find and replace puissant et ultimement arriver a comprendre les chaînes elles même (le vrai parsing!). Bon si on continue dans nos groupements, les () servent a faire un ensemble qui est "catché" (attrapé).. pour l'instant ça donne rien d'intéressent : $a =~ /(meuh)/; et si jamais on trouve "meuh" dans $a, et bien une variable magique nommé $1 va être créer a la prochaine ligne, et elle va contenir ce qui est entre (), donc "meuh". Pas très utile pour l'instant, mais c'est la base du parsing. Bon maintenant qu'on a vu les différentes parenthèses, on va voir ce que je voulais dire par "2 fois la même chose". En regex ya plusieurs caractères spéciaux, en faite y'en a une tonne, tellement que certains change en fonction des parenthèses.. par exemple le ^ de tantôt, qui entre [] donne une négation, si on la mets entre // (donc par défaut), ça va tenter de matcher au début de la string uniquement. Donc, $a =~ /^meuh/; va retourner un true seulement si meuh est au début de la ligne. si $a = "blah meuh", ça va être false. Un autre exemple du genre le point (.). Il sert a matcher n'importe kel caractère entre // mais entre [] il va matcher le caractère . lui-même. Donc, $a =~ /./; # true si ya au moins un caractère dans $a $a =~ /[.]/; # true si ya au moins un . dans $a $a =~ /\./; # true si ya au moins un . dans $a Et oui, le \ sert a "backslasher".. hehe Donc dès que vous voulez matcher un caractère spéciaux en regex, vous avez qu'a mettre \ devant, et Perl va s'occuper de matcher le vrai caractère.. $a =~ /\[/; # match un [ A part ça il y a le | qui est intéressent. Il permet de faire un OU dans le match, donc au lieu de faire : $a =~ /meuh/ or $a =~ /pwet/; on peut faire facilement : $a =~ /meuh|pwet/; # match meuh ou pwet Ensuite de tça, il y a le $, qui sert a matcher a la fin de la string (comme ^ mais à la fin).. Donc, $a =~ /meuh$/; # match seulement si meuh est a la fin de la string $a Evidemment on peut aussi matcher autre chose que des caractères normaux, par exemple on peut matcher un retour de ligne \n un form feed \f un tab \t ou n'importe kel espacement \s. Donc $a =~ /\s/; # match si ya un espace (un " " ou un "TAB" dedans) Tout ça est bien plaisant, mais maintenant si vous voulez commencer a faire des matchs plus complets, il vaut faudra matcher autre chose que juste des "mots définies" tel que pwet ou meuh. Pour ce faire on utilise 2 choses, la première les caractères spéciaux \w et \d, qui remplace respectivement [A-Za -z_] et [0-9]. Donc \w match une lettre ou un underscore.. et si on en veut plusieurs il faut utiliser des quantifieurs. Les quantifieurs sont les suivants : ? pour 0 ou une fois + pour 1 ou plusieurs fois * pour 0 ou plusieurs fois {1,5} pour de 1 fois à 5 fois On peut donc les appliquer sur ce qu'on veut dans les caractères pour matcher : $a =~ /a+/; # match 1 ou plusieurs a $a =~ /.{2,4}/; # match 2, 3 et 4 caractères $a =~ /\d?/; # match 0 ou 1 chiffre $a =~ /\w*/; # match rien pentoute ou plusieurs [A-Za-z_] (useless un peu..) Faite attention cependant quand vous mettez entre () des affaires, par exemple (\w)+ va juste mettre dans $1 une seule lettre (\w+) va mettre le mot au complet (plusieurs lettres) dans $1 Parlant de $1 et compagnie, assayer de figurer le coté pratique de la chose. Si vous avez $a =~ /blah(\d+)/; Vous allez vous ramasser a la ligne suivante avec un chiffre dans $1. Si vous êtes pas sur si c'est $1 ou $2 la derniere variable matcher, par exemple $a =~ /"(\w+)"|'(\w+)'/ utiliser $+, qui va vous retourner le dernier match. Maintenant que vous êtes parti en peur avec les $1, vous avez surement envie d'utilisé le $1 dans le même match, du genre : $a =~ /blah(\d+).*meuh($1)/ # ERREUR! Malheureusement perl va checker le $1 d'avant car ya pas fini d'exécuter la regex. Donc, pour faire ce que vous voulez (matcher 2 fois les memes digits \d+) il vous faut : $a =~ /blah(\d+).*meuh(\1)/ # fun fun Une utilité pratique des regex se retrouve dans le "find and replace". Il arrive souvent qu'on veulent éditer le contenue d'une variable live (exemple quand on trim les inputs/outputs vers des sites web pour empêcher le XSS). En perl on peut simplement faire la substitution de la manière suivante : $a =~ s/pwet/meuh/; Ainsi tout ce qui match pwet dans $a va être changer par meuh. Si jamais il y a plusieurs pwet dans $a et on veut tout les changer, on doit utiliser /g : $a =~ s/pwet/meuh/g; qui va prendre tout les pwet et les changer en meuh. N'oublier pas que toute ce qui est matcher va être remplacer, donc si on fait $a =~ s/\d/_/g; ça va remplacer tout les digits [0-9] par _ dans $a. Perl offre un moyen simple pour faire du find and replace dans un fichier : perl -p -i -e 's/pwet/meuh/g' fichier.txt va faire la job dans le fichier.txt. Un peu plus pratique que sed car vous avez pas a vous faire chier avec le output et le mettre dans un fichier temporaire. Le -p sert a parcourir tout le fichier, le -i sert a remplacer la ligne dans le fichier, et le -e pour exécuter directement ce qu'il y a entre ''. Parlant de fichier, perl offre aussi une manière de modifier une variable qui contient plusieurs lignes, et la traiter ligne par ligne (un peu comme l'exemple précédent). Pour ce faire vous devez utiliser /m qui s'occupera de traiter la variable comme une variable a multiple lignes, donc de refaire la regex sur chaque ligne au lieu que sur le tout. Ce que ça donne? Eh bien on va "slurper" (prendre le contenu intégral) un file dans une variable pour voir: undef $/; # enable le "slurp" $slurped = <>; # on slurp! En suite on peut s'amuser a faire des matchs sur $slurped: $slurped =~ s/^$//mg; # efface les lignes vides $slurped =~ s/^\s*$//mg; # efface les lignes vides avec des espaces $slurped =~ s/^$//mgx; # efface les lignes vides avec des espaces ( le /x sert a ignorer les espaces.) Si on aurait omit le /mg dans la premiere ligne, on aurait assayer de matcher la totalité de $slurped avec la regex (donc on aurait vérifier si $slupred est vide). Les regex étant vaste, et ceci étant seulement un tutorial, je vais vous envoyez quelque bidules en vrak intéressent, mais dites vous que en réfléchissant un peu, vous pouvez arriver a faire tout ce que vous voulez avec ces merveilleuses regex... $myregex = qr/aaa/i; # permet davoir une regex dans une variable $pwet =~ $myregex; # dont peut être utiliser comme ça $ayoye =~ /(.+)*/; # ligne carrément stupide car va assayer de matcher # n'importe quoi. utilitée? benchmark de cpu ;-) Un concept intéressent, le lookahead (?=) et lookbehind (?<=) $a =~ s/(?<=meuh)(?=pwet)/_/g; # va remplacer meuhpwet par meuh_pwet $a =~ s/(?<=)pwet/_pwet/g; # meme chose $a =~ s/meuh(?=pwet)/meuh_/g; # meme chose $a =~ s/?<=\bmeuh)(?=pwet\b)/_/g; # encore plus puissant car va remplacer "a meuhpwet unf" par "a meuh_pwet unf" et "ameuhpwetunf" va pas matcher. Le \b sert a dire que c'est le "boundarie" du mot. Donc que ça va jusqu'au espaces. A ne pas confondre avec \b... haha n'oublier pas que certaines choses peuvent changer s'il ne sont pas entre (). \b sans les () sert a matcher le caractère backspace. Perl Functions ¯¯¯¯¯¯¯¯¯¯¯¯¯¯ > >> Quoi faire avec un hash le samedi soir > Afficher les clefs d'un hash print keys %pwet; > Qui sert à afficher le contenue du hash foreach (keys %pwet) { print "\$pwet{$_} = $pwet{$_}\n"; } > Si je veux rien savoir des clefs, mais je veux que la liste des valeurs print values %pwet; > Si je veux les clefs et les valeurs sans trop d'efforts while ( ($clef, $valeur) = each %pwet ) { print "\$pwet{$clef}{$valeur}\n"; } > Remarquer dans le dernier exemple que on utilise un while et non un foreach. La fonction each itère à travers le hash, au lieu de carrément retourner une liste. L'avantage de tça c'est si le hash est vraiment big, on aura pas une grosse copie de mémoire pour rien. De plus, si le hash est tie à un gros file en netbios via tokering sur un serveur 286, utiliser keys/values va etre slow car il va toute slurper le hash d'une shoot, tandis que each va y aller clef/valeur par clef/valeur et donc le processing va se faire pendant que ça lit au lieu d'après. > Si on veut scrapper une entry dans un hash a partir de sa clef if (exists $pwet{meuh}) { delete $pwet{meuh}; } Si jamais vous faite $pwet{meuh} = ""; vous n'allez pas scrapper la clef, mais simplement effacer la valeur. Même chose si vous faite undef $pwet{meuh} ça va mettre la valeur sous la clef meuh a undef et non l'effacer. D'où l'utilité du delete. En passant, si vous travailler avec des références, faites très attention car if (exists $pwet->{a}->{b}) {;} va "autovivifier" le hash. Donc perl va créer lui meme les référence quand vous allez faire le exists. Comme quoi des fois les features deviennent facilement des bugs. > >> Quoi faire avec un array après le hash du samedi soir > On a déjà vu dans les tuto précédents les fonction push/pop/shift/etc. De plus, les fonctions exist et delete fonctionne aussi avec les array. foreach (0..$#pwet) { delete $$pwet[$_]; } même chose que delete @pwet[0..$#pwet]; même chose que @pwet = (); et même chose que undef @pwet; Si vous voulez effacer TOUT le contenu d'un array, c'est mieux d'y aller avec un = () ou un undef, mais par contre les autres on leur utilité quand vous voulez delete une partie. En passant si vous faite un delete sur $pwet[2], le $pwet[2] va pu exister dans un foreach @pwet, mais si l'espace pour la valeur [2] va être libre, donc un push va mettre ça dans [3]. > splice permet de faire des choses cool, car vous prennez un array, aller à un offset, prendre une longueur et vous remplacer ça par ce que vous voulez, voici des équivalences : pop(@a) <=> splice(@a, -1) shift(@a) <=> splice(@a, 0, 1) $a[$x] = $y <=> splice(@a, $x, 1, $y) > >> La magie du undef (c'est pas une fonction mais ça peut en être une.. hum) > undef est spécial en perl. Il ne s'agit pas de "" (string vide) ni de 0 (valeur numérique 0), il s'agit de rien pentoute. Et ce rien pentoute ne peut pas être utiliser dans un if. Vous devez donc faire : if (defined $var) {;} pour voir si la variable en question est a undef. En plus, undef agit comme un opérateur et permet de scrapper la variable undef $var; Et comme si c'était pas assé, on peut se servir de undef comme valeur $var = undef; On peut donc arriver a faire des choses folles tel que skipper une assignation (maintenant undef rempalce une variable!) my ($a, $b, undef, $c) = @pwet; # $pwet[2] va être perdu dans malle et tant qu'a faire, on peut faire une chose totalement inutile! undef = $pwet; # fait rien pentoute ;-) > >> un foreach sans foreach qui retourne une liste de $_ > Reprennez votre souffle, je voulais juste dire "map" au lieu de se titre la. mais bon mon titre est plus explicatif que map, malgres que le concept est fun : foreach (@meuh) { print; } va donner la meme chose que les lignes suivantes (qui donnent eux-mêmes toute la meme chose!). En passant, n'oublier pas que $_ existe par "défaut".. map { print } @meuh; map { print $_ } @meuh; map print, @meuh; > map retourne aussi une valeur : la liste des $_ changer, donc peut faire un genre de replace ou de processing avec un array : @pwet = map { lc } @meuh; # les valeurs de @meuh dans @pwet en lowercase! > >> un foreach sans foreach qui retourne une liste sélective de $_ > Aussi appellé @pwet = map { EXPR ? $_ : () } @meuh; Mais appellé par ses amis comme étant le "grep" de perl.. @pwet = grep { EXPR } @meuh; # même chose que le map de lautre ligne L'EXPR se trouve à être un match avec $_, on peut donc y mettre une regex @subdir = grep !/\.|\.\./, @DIRLIST; # enleve les dirs . et .. de # DIRLIST et fout ça dans @subdir Donc on peut arriver a faire ça : @adultes = grep { $bleh{$_}{age} >= 18 } keys %bleh; cool. heh. Bon voila, on vient de voir quelques affaires puissant en perl. Dites vous que rendu a ce tuto la, le reste ce qu'il reste a voir en perl, c'est soit des modules ou des extensions par défaut de Perl, mais la la base, nous l'avons presque tout couvert (sans bien sur avoir couvert les subtilités, qui sont très subtiles d'ailleurs) ce que Perl peut offrir. Mais bon, avec les zillions de modules disponible sur CPAN, et les tonnes de modules par défaut, le plaisir de continuer a apprendre avec Perl peut se poursuivre jusqu'à l'infini.. à l'infini.. à l'infini... Note : Cet article remporte le record guiness (vous savez la bière la) de l'article le plus long a produire de l'histoire. Il a été écrit sur papier en décembre 2003, et (vu que le #4 de mes articles de perl était déjà assé long) garder en archive, pour être écrit au 1/4 quelques semaines après. Cependant le dernier jet a été donner en août 2004, soit 8 mois après son écriture. Beat this. - LastCall_ .----------------------------------------------- - - --- - --- | LastCall_ [lastcall at mindkind dot org] www.mindkind.org '------ -- ----------- -- - - ---------- - - -- - - -- ----- - - - - -