PHP 5.5 est sorti le 20 juin, un peu plus de 1 an après la version 5.4 (1er mai 2012)
Sommaire
-
Nouveautés de PHP 5.5
- Générateurs et coroutines
- Mot-clef finally
- API simplifiée pour hasher des mots de passe
- Déréférencement des tableaux/chaînes de caractères constants
- Résolution de nom de classe scalaire
- empty() sur les appels de fonctions et expressions
- Clefs d'itération non-scalaires
- Utilisation de list() dans les déclarations foreach
- Extension Zend OPcache
- Mise à jour de la bibliothèque GD
- Mises à jour de compatibilité
Nouveautés de PHP 5.5
Générateurs et coroutines
Le support des générateurs a été ajouté via la directive yield
, que l'on peut retrouver par exemple, en C#.
Un générateur est une fonction permettant de générer des éléments, comme le fait la fonction range
.
Dans l'exemple ci-dessous, on crée un générateur de suite fibonacci pour permettre d'itérer dessus :
function fibonacci($limit = 50) {
$cur = 1;
$last = 0;
for ($i=1;$i<$limit;$i++) {
yield $cur;
$tmp = $last;
$last = $cur;
$cur = $last + $tmp;
}
}
echo "Suite de Fibonacci: ";
foreach (fibonacci(10) as $nb) {
echo $nb." ";
}
echo "\n";
Ce qui nous donnera :
Suite de Fibonacci: 1 2 3 5 8 13 21 34 55 89
Mot-clef finally
Avant la version 5.5 la gestion des exceptions PHP était effectuée par le biais des opérateurs try
et catch
. L'opérateur catch
était appelé lorsqu'une exception était levée par l'appel de la fonction throw
.
Le mot-clef finally
a été introduit, permettant de combler un manque dans PHP par rapport à d'autres langages comme Java ou Python. finally
permet d'ajouter un bloc de code à exécuter, même si une exception a été levée.
Exemple :
$con = NULL;
try {
$con = mysql_connect("localhost","myuser","mypwd");
if (!$con) {
throw new Exception("Echec de connexion à la base de données");
}
catch {
echo 'Erreur MySQL : ', $e->getMessage(), "\n";
}
finally {
if ($con !== NULL) {
close($con);
}
}
API simplifiée pour hasher des mots de passe
Cette nouvelle API permet de hasher rapidement des mots de passe, visualiser quelques infos sur le hash et vérifier leur validité. À l'heure actuelle, seul blowfish
est supporté.
Hashage
La fonction password_hash
permet d'effectuer ce hashage.
string password_hash ( string $password , integer $algo [, array $options ] )
Les paramètres passés sont :
- le mot de passe en clair;
- l'algorithme de hash: PASSWORD_DEFAULT et PASSWORD_BCRYPT - à noter que PASSWORD_DEFAULT correspond pour l'heure à PASSWORD_BCRYPT;
- des options:
-
salt
: un grain de sel afin de complexifier le hash -
cost
: le coût du hash. Plus la valeur est élevée plus le hash est robuste et long à générer.
-
$options = [
'salt' => "free-software",
'cost' => 18,
];
echo password_hash("linuxfr", PASSWORD_DEFAULT, $options);
Comparaison du hash et du mot de passe
La fonction password_verify permet de comparer un mot de passe à un hash défini (par exemple en base de données)
boolean password_verify ( string $password , string $hash )
Vérification de l'algorithme de hash
La fonction password_needs_rehash
permet de vérifier que le hash donné correspond bien à l'algorithme et aux critères de complexité donnés.
boolean password_needs_rehash ( string $hash , string $algo [, string $options ] )
Informations sur un hash
Enfin, la fonction password_get_info
permet d'obtenir les informations sur un hash (algorithme, options).
array password_get_info ( string $hash )
La fonction retourne un tableau contenant 3 éléments :
- algo : l'algorithme au format constante
- algoName : le nom de l'algorithme sous forme lisible
- options : les options passées lors du hashage du mot de passe (coût et grain de sel)
Déréférencement des tableaux/chaînes de caractères constants
Il est désormais possible d'accéder à des éléments atomiques dans un tableau ou une chaîne de caractères.
echo "linuxfr"[3]."\n";
echo ["bsd","linux","unix"][1]."\n";
retournera :
u
linux
Résolution de nom de classe scalaire
Cette nouvelle notion permet de retrouver le nom d'une classe.
class MyClass1 {}
echo "Le nom de ma classe est: ".MyClass1::class."\n";
retournera :
Le nom de ma classe est: MyClass1
Si vous utilisez les espaces de noms, la fonction affichera également l'espace de noms associé.
namespace MyNameSpace;
class MyClass1 {}
echo "Le nom de ma classe est: ".MyClass1::class."\n";
retournera :
Le nom de ma classe est: MyNameSpace\MyClass1
empty() sur les appels de fonctions et expressions
Auparavant empty ne pouvait être utilisé que sur des variables PHP. Il est désormais possible de l'utiliser sur des appels de fonctions et expressions.
# PHP 5.4
$var = myFunc();
if (empty($var)) {
// quelque chose
}
$var2 = false;
if (empty($var2)) {
// quelque chose d'autre
}
# PHP 5.5
if (empty(myFunc())) {
// quelque chose
}
if (empty(false)) {
// quelque chose d'autre
}
Clefs d'itération non-scalaires
Added support for non-scalar Iterator keys in foreach.
Utilisation de list() dans les déclarations foreach
Il est désormais possible d'appeler la fonction list dans un foreach:
$array = [
["MySQL", "beaucoup utilisé"],
["PostgreSQL", "monte"],
["Oracle", "trop cher"] ];
foreach($array as list($name,$adj)) {
echo $name." est ".$adj."\n";
}
retournera :
MySQL est beaucoup utilisé
PostgreSQL est monte
Oracle est trop cher
Extension Zend OPcache
Cette nouvelle extension est dédiée à l'amélioration des performances de PHP. Elle permet de stocker le bytecode généré par PHP dans la mémoire partagée, évitant ainsi de relire et parser les scripts à chaque exécution.
Mise à jour de la bibliothèque GD
GD passe en version 2.1 et intègre des améliorations de fonctions existantes et des correctifs.
De nouvelles fonctionnalités intéressantes ont également été ajoutées :
- retournement d'images via la fonction
imageflip
; - recadrage avancé via les fonctions
imagecrop
etimagecropauto
. - lecture et écriture du format d'images WebP via
imagecreatefromwebp
etimagewebp
.
Mises à jour de compatibilité
- les GUID du logo PHP ont été supprimés;
- Windows XP et Windows Server 2003 ne sont plus supportés ( ouf );
- la sensibilité à la casse n'est plus locale. L'insensibilité à la casse des nom des fonctions, classes et constantes est gérée localement et de manière indépendante, conformément aux règles ASCII.
Aller plus loin
- PHP 5.5.0 Release Announcement (142 clics)
- Changelog complet de PHP 5.5 (131 clics)
# finally
Posté par Arcruxe . Évalué à 6.
"finally" n'existe pas en C++, et il n'y en a pas besoin, vive RAII :)
En revanche il me semble que Microsoft l'a rajouté dans son C++ version .NET…
[^] # Re: finally
Posté par Loïc Blot (site web personnel) . Évalué à 2.
Effectivement, il s'agit d'une erreur de ma part. Si un modérateur peut corriger
Veepee & UNIX-Experience
[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 1.
Quel est le rapport avec RAII ? Même si cette technique permet de libérer une ressource qu'on a oublié de libérer, il est toujours de bon ton de libérer la ressource après usage. Histoire qu'une exception lancée à la libération de la ressource ne vienne pas tout casser dans le destructeur appelant, par exemple.
[^] # Re: finally
Posté par Arcruxe . Évalué à 7.
http://www.stroustrup.com/bs_faq2.html#finally
[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à -2.
Et je te renverrai au « Exceptional C++ » de Herb Sutter, qui conseille fortement de libérer une ressource avant de sortir du scope.
Évidement, la ressource devrait être correctement libérée en cas d'exception, mais dans le cas où aucune exception n'est levée, il est tout indiqué de libérer la ressource inutile, pour éviter qu'une exception ne soit lever dans un destructeur.
[^] # Re: finally
Posté par Arcruxe . Évalué à 1. Dernière modification le 01 juillet 2013 à 20:21.
Je n'ai pas ce livre, donc si tu pouvais étayer un peu ton propos histoire que je comprenne où tu veux en venir…
Je ne vois pas ce que les destructeurs viennent faire dans l'histoire, et encore moins d'où sortirais la fameuse exception qu'il leverait…
Sans le f.close(), la ressource est correctement libérée, qu'il y ait une exception ou pas, puisque le destructeur va s'en charger.
[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 03 juillet 2013 à 12:00.
Prenons un exemple. Imaginons une classe qui prend en charge l'ouverture d'un fichier, et la gestion de son buffer. L'ouverture du fichier est fainéante, l'écriture aussi, et la fonction close lance l'écriture du buffer en dernier ressort. Une exception io_error est lancée lorsqu'une erreur d'écriture est détectée :
Le résultat sur la ligne de commande :
mickael@laptop:~/tmp$ ./throwing_close
Catched exception!
terminate called after throwing an instance of 'my_ns::io_error'
Abandon
Alors, évidement, ici la classe est facile à repenser, si toutefois elle faisait quelque chose. Mais imagine une architecture plus complexe, où l'exception safety n'est jamais garantie, où aucune signature n'indique qu'une fonction peut lancer une exception, où la libération d'une ressource peut être complexe et entraîner moult lancement d'exception.
Oui, il faut libérer les ressources dès qu'on le peut, ou des comportement indéfinis sont garantis (ici on notera que l'implémentation tue directement le processus qui aura tenté de lancer une exception depuis un destructeur, il n'est pas possible d'empêcher sa destruction).
Donc non, même en utilisant un objet RAII, il n'y a pas de garantie à ce que la ressource soit libérée si une exception peut être levée lors de l'exécution d'un destructeur.
[^] # Re: finally
Posté par Arcruxe . Évalué à 1.
Ton destructeur balance une exception, évidemment que ça se passe mal, il ne faut jamais lancer d'exceptions dans un destructeur.
Si tu veux vraiment laisser le fonctionnement de ta classe tel quel (c'est-à-dire écriture fainéante), tu dois par exemple attraper l'exception de flush dans ton destructeur :
http://www.stroustrup.com/bs_faq2.html#ctor-exceptions
Le problème c'est pas RAII, c'est que tu lances une exception dans le destructeur…
[^] # Re: finally
Posté par ckyl . Évalué à 1.
Oui c'est vachement mieux de jeter ca sous le tapis ni vu ni connu ;)
Pourquoi tu flush ton flux tout le monde s'en fou quand ca s'est mal passé ? Autant ne pas le faire.
Dans les fait j'ai un énorme doute sur le fait que tu puisses toujours gérer correctement une erreur au niveau du destructeur. C'est assez fréquent qu'il faille remonter de X niveau business pour faire ce qu'il y a faire. En général en bas niveau on peut juste remonter que ca à foiré mais l'error recovery est bien plus haut.
Je ne fais pas de C++ mais j'ai du mal à voir en quoi ca serait spécifique sur ce point.
[^] # Re: finally
Posté par Arcruxe . Évalué à 1. Dernière modification le 03 juillet 2013 à 20:20.
J'ai pas non plus dit de jeter ça sous le tapis… Mon exemple affiche un message d'erreur. On peut aussi écrire un truc dans un fichier de log, envoyer un mail à la supervision, appeler std::terminate soi-même, etc. Il y a de nombreuses solutions, mais aucune n'est vraiment satisfaisante. La meilleure chose, c'est encore de faire en sorte de pas avoir d'erreurs à gérer (ou le moins possible) dans les destructeurs. Donc éviter les traitements trop complexes. Si on a besoin d'en faire, à mon humble avis, c'est qu'on a un problème d'architecture dans le programme…
Par exemple si je reprends l'exemple de l'écriture fainéante, on peut faire un singleton (qui a la même durée de vie que le programme) qui s'occupe de gérer tous les fichiers ouverts et de les flusher quand nécessaire. Ou alors (et à mon avis c'est encore le mieux pour cet exemple) on fait pas d'écriture fainéante dans notre programme et on laisse l'ordonnanceur d'entrées/sorties du noyau faire son boulot.
Pour être honnête, je trouve ça vraiment gênant cette histoire d'écriture fainéante, puisque quoi qu'il arrive, on ne peut pas remonter les erreurs à l'appelant au bon moment.
On peut aussi considérer que ça fait partie de la sémantique de la classe : les écritures peuvent échouer sans pour autant remonter d'erreurs… Auquel cas l'appelant doit en être parfaitement conscient et architecturer son code en conséquence.
Ou sinon il faut expliquer à Bjarne Stroustrup que lancer des exceptions dans un destructeur c'est bien et que c'est pas grave de pouvoir se retrouver avec deux exceptions à gérer en même temps ou autre joyeuseté…
[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 1.
L'écriture fainéante (et tout processus fainéant, comme l'overcommit memory, COW, etc) est toujours difficile à déboguer, puisque c'est un procédé par nature asynchrone. Et ceci me pose autant de problèmes qu'à toi. Malheureusement, les humains n'aiment pas attendre, et parfois il faut abandonner un peu de fiabilité (oui, ça me fait mal de dire ça) pour un peu de confort (rapidité visuelle).
Je préfère que le programme plante, histoire de ne pas avoir un état incohérent, et que le couillon qui gère l'administration du produit ne se dise pas que ce n'est qu'un « warning ».
Oh oui, mets-moi du singleton là où il n'y en a pas besoin. D'ailleurs, par durée de vie du programme, tu entends une variable statique instanciée lors de la création du process (et donc un comportement indéfini à la clé) ? Oui, le j'ai déclaré le singleton « ennemi public numéro un », puisqu'il est le vengeur masqué de la variable globale.
Ici j'ai pris l'exemple d'une classe qui écrit un fichier pour rester simple. Mais on peut imaginer un ORM, qui flush les données modifiées, ajoutées et supprimées vers la base de données. Dans ce cas, le caractère asynchrone de la communication avec un serveur distant est pertinent, et les exceptions peuvent alors être légion.
Il semble que tu ne comprennes pas pourquoi lancer une exception depuis un destructeur est problématique. Le problème n'est pas le nombre d'exceptions à gérer, mais le fait que la destruction d'un objet est suspendue par la levée de l'exception, et qu'on se retrouve alors avec un objet partiellement détruit, ce qui pose des problèmes de fiabilité.
[^] # Re: finally
Posté par Arcruxe . Évalué à 1.
Donc a priori la solution qui te plairait le mieux ça serait de donner un "handler" (pointeur de fonction ou foncteur) à l'objet, qu'il peut appeler en cas d'erreurs sur une tâche asynchrone (voir mon autre post).
Pas nécessairement non.
Et d'ailleurs une variable static globale n'est pas un comportement indéfini, il faut juste être très prudent quand on en utilise (j'évite toujours personnellement).
C'est bien pour ça que c'est au destructeur de faire quelque chose d'approprié quand il rencontre un problème, au lieu de lèver une exception / en laisser une se propager à l'appelant.
Et du point de vue du fonctionnement du langage, c'est effectivement de se retrouver avec deux exceptions à gérer en même temps qui est problématique.
http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html
Une autre façon de voir les choses : une fonction doit lever une exception si et seulement si ses post-conditions ne peuvent pas être remplies. L'unique post-condition d'un destructeur est que l'objet n'existe plus en mémoire. Cette post-condition est toujours remplie quoi qu'il arrive. Donc un destructeur ne peut jamais légitimement lever d'exceptions.
[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 2.
Non, l'exception n'est pas lancée dans le destructeur. Elle est lancée indirectement depuis le destructeur. Et c'est bien le point que j'essaye de mettre en exergue, et que tu refuses de voir : le RAII c'est très bien, ça permet de mieux gérer les ressources. Mais ce n'est pas magique. Et donc, il faut toujours penser à libérer les ressources dès qu'on peut.
Maintenant, je m'attendais à ce que tu vienne avec une solution qui est encore pire qu'un segfault : attraper l'exception dans le destructeur, et continuer comme si de rien n'était (oui, cracher dans std::cerr revient à ne rien faire). Pourquoi est-ce une mauvaise chose ? Premièrement, parce qu'on ne sait pas dans quel état on est. C'est d'ailleurs pour ça que le programme se termine quand une exception est balancée depuis un destructeur : c'est la panique. Deuxièmement, que se passe-t-il si une exception d'une autre nature est lancée depuis flush ? On pourrait très bien rajouter un catch-all, mais je doute qu'il soit pertinent d'attraper tout et n'importe quoi, et gérer les exceptions n'importe comment.
Alors oui, j'ai déjà vu ça dans du code en production. MongoDB au hasard. Mais ils se définissent eux-même comme des anti-c++ (oui, c'est amusant quand on sait en quel langage est écrit MongoDB), ce qui montre quel type de personnes gèrent la gestion des ressources par-dessus l'épaule.
[^] # Re: finally
Posté par Arcruxe . Évalué à 0.
Tu pinailles, ça revient exactement au même…
Voir mon autre post en réponse à ckyl.
De mon point de vue, c'est justement le travail du destructeur de faire quelque chose d'approprié quand il lève une exception. Justement pour avoir un état valide à la fin. Et, si ce n'est pas possible, appeler std::terminate dans le destructeur est acceptable. Une autre solution pas trop mal, c'est de donner un "handler" à l'objet (pointeur de fonction / foncteur), qu'il peut appeler en cas d'erreurs sur une tâche asynchrone (y compris dans le destructeur). Comme ça, le programme peut faire ce qui lui semble approprié dans ce cas (et il est certainement mieux placé que le destructeur peut savoir ce qui est "le mieux" à faire d'ailleurs).
Non, ton exemple s'est retrouvé dans std::terminate parce qu'une exception n'a pas été attrapée : l'exception lancée dans le destructeur de l'objet du premier morceau de code du main, après le catch (celui qui affiche "Catched exception"). Le deuxième morceau de code du main n'est, par conséquent, pas exécute (s'il était, le catch serait exécuté comme attendu et "That can't be catched" serait affiché sur la sortie standard).
Appeler flush et close explicitement dans l'appelant ne résout pas le problème…
[^] # Re: finally
Posté par Batchyx . Évalué à 6.
Lancer des exceptions dans un destructeur en C++, c'est le mal absolu. Il y a même des cas ou le faire provoque un comportement indéfini.
Pour le reste, le RAII remplace le
finally
. Tu peux même faire une macro qui ressemble à unfinally
mais qui utilise du RAII en dessous: http://www.boost.org/doc/libs/1_53_0/libs/scope_exit/doc/html/index.html[^] # Re: finally
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 4.
En fait, ce ne sont pas des cas : le comportement est indéfini.
[^] # Re: finally
Posté par claudex . Évalué à 4.
Corrigé, merci.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
# foreach() + list()
Posté par Artefact2 (site web personnel) . Évalué à 3. Dernière modification le 30 juin 2013 à 12:19.
Il me semble que l'exemple n'est pas bon. Voici ce qu'il devrait être:
Pas trop d'intérêt pour cet exemple, mais bon. C'est juste du sucre syntaxique pour remplacer:
Aussi, aucune mention de la nouvelle fonction
array_column()
qui est pourtant bien utile!J'attends avec hâte qu'il soit disponible dans les paquets Arch, PHP 5.5 apporte à mon avis d'excellente nouveautés et va dans la bonne direction.
[^] # Re: foreach() + list()
Posté par Loïc Blot (site web personnel) . Évalué à 1. Dernière modification le 30 juin 2013 à 16:52.
Je ne connaissais pas array_column, j'ai regardé le changelog détaillé mais je ne l'ai pas vu. C'est utile dans certains cas (assez rares je pense non ?).
Je confirme mon erreur sur l'array, j'ai mal lu l'article, désolé. Si un modérateur peut corriger l'exemple ?
Veepee & UNIX-Experience
[^] # Re: foreach() + list()
Posté par claudex . Évalué à 3.
Je n'ai pas compris la différence entre les deux et ce que devient
$adj
.« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: foreach() + list()
Posté par Loïc Blot (site web personnel) . Évalué à 1. Dernière modification le 30 juin 2013 à 17:08.
En fait, $name est le premier élément du tableau imbriqué et $ajd le second élément.
http://fr2.php.net/manual/en/control-structures.foreach.php#control-structures.foreach.list
Veepee & UNIX-Experience
[^] # Re: foreach() + list()
Posté par claudex . Évalué à 3.
Voilà, je pense avoir corrigé correctement.
« Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche
[^] # Re: foreach() + list()
Posté par Loïc Blot (site web personnel) . Évalué à 1.
Niquel merci !
Veepee & UNIX-Experience
[^] # Re: foreach() + list()
Posté par Artefact2 (site web personnel) . Évalué à 1.
Ça dépend du code, des fois ça ne saute pas aux yeux qu'on peut remplacer un bloc par un
array_column()
. D'ailleurs, je me demande si la fonction est implémentée en interne avec un générateur… (Pas trop d'intérêt sinon)[^] # Re: foreach() + list()
Posté par LupusMic (site web personnel, Mastodon) . Évalué à 0.
Je ne pense pas, array_column ne prend pas de référence du tableau en paramètre. Ceci dit, pourquoi un générateur ? pour une microoptimisation inutile qui prend plus de ressource que si on réduit le tableau ?
# Red Hat a raison
Posté par kadalka . Évalué à -10.
C'est aussi pour pouvoir utiliser les dernières versions de PHP que Red Hat a du créer Red Hat Collection… :-)
Si ma mémoire est bonne le WampServer d'alterway permettait déjà depuis longtemps de mettre différents PHP ou MySQL…
En tout cas, il me semble que le dernier Symfony (2.3) exige du PHP 5.3+ et certaines distributions n'ont pas dans leur LTS du PHP 5.3.
Pour ceux qui ne connaissent pas Symfony: High Performance PHP Framework for Web Development - Symfony
Merci pour les détails…
# PHP makes me sad
Posté par Anonyme . Évalué à 2.
PHP Sadness
[^] # Re: PHP makes me sad
Posté par ʭ ☯ . Évalué à 0.
Ayn language makes me
sadhappy.⚓ À g'Auch TOUTE! http://afdgauch.online.fr
# finally et sel des hashs
Posté par MCMic (site web personnel) . Évalué à 3.
Il y a deux choses que je n'ai pas saisi:
Déjà, à quoi sert finally? Je ne comprends pas l'explication, quelle est la différence entre:
et
?
Autre question, ces histoires de sel de mot de passe me paraissent étranges, comment password_get_info peut connaître le sel du hash?
Et comment password_verify peut vérifier sans connaître le sel? (c'est un peu la même question)
Et du coup, puisque ces deux choses semblent possible, quel est l'intérêt du sel?!
PS: et comment on active la coloration syntaxique PHP?
[^] # Re: finally et sel des hashs
Posté par windu.2b . Évalué à 6.
finally
garantit que le code qu'il contient sera exécuté après le try/catch, quelque soit l'issue (bonne ou mauvaise).En fait, pour bien comprendre, il faudrait plutôt un exemple comme celui-ci :
Ici, le catch écrit un message d'erreur puis renvoie l'exception levée, donc on sort complètement de la fonction. Ce qui suit (le
if
de fermeture de la connexion) n'aurait jamais été exécuté s'il ne se trouvait pas dans unfinally
[^] # Re: finally et sel des hashs
Posté par MCMic (site web personnel) . Évalué à 3.
Merci, c'est beaucoup plus clair.
[^] # Re: finally et sel des hashs
Posté par Loïc Blot (site web personnel) . Évalué à 1.
Hello, désolé du retard,
pour ta question concernant les mots de passe, eh bien, j'avoue que je suis resté assez perplexe aussi…
Pour la fonction password_verify, le sel utilisé est introduit dans le hash final, regarde ici: http://www.php.net/manual/fr/function.password-hash.php
Pour la coloration syntaxique j'ai utilisé celle de PERL
Veepee & UNIX-Experience
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.