présente...

 

Chapitre : Développement

par tous les amis de Léa

 

 

Le copyright de chaque article est détenu par son auteur.
Le copyright du livre lui-même est détenu par Léa (Association Loi 1901).
Les articles de ce livre sont diffusés selon la license GPL (GNU General Public License), sauf mention explicite dans l'article.

Copyright (c) 2003 Association Léa.
This documentation is free documentation; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This documentation is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
For the purpose of applying the GPL to this document, I consider "source code" to refer to the html source.


Table des matières


Linux : plateforme de développement

Cette section contient les chapitres relatifs aux outils de développement sous Linux, et à des langages qui y sont couramment utilisés.

Pour vos questions concernant le développement dont vous ne trouvez pas la réponse ici, allez voir le forum développement...


Haut


Introduction à (g)awk

par Xavier GARREAU

Cet article va vous permettre de vous familiariser avec (g)awk, langage de programmation permettant de faire très rapidement des traitements intéressants sur des données diverses. Après la présentation générale, on réalisera deux filtres inspirés de filtres ayant été réellement développés.


Introduction

Présentation

Vous vous demandez surement pourquoi ce (g) avant awk, non ? Ce n'est pas le g de gnome, ni celui de gtk+ mais celui de gnu. Extrait de la page de manuel (man gawk ou man awk) :
"Gawk est l'implémentation du projet GNU du langage de programmation AWK, lequel se conforme à la définition du langage dans la norme POSIX1003.2 Command Language and Utilities Standard. Cette version est basée, elle, sur la description d'AWK de Aho, Kernighan and Weinberger, plus les spécificités supplémentaires se trouvant dans la version de awk de l'UNIX System V version 4. Gawk ajoute également des extensions plus récentes des Laboratoires Bell ainsi que des extensions spécifiques à GNU."
Je ne mets pas ce pavé pour remplir de l'espace mais pour que les personnes à la base de choses que je ne fais que présenter soient reconnues pour leur travail et que l'on sache de quoi on parle.

Nous allons pouvoir commencer:

Qu'est ce que c'est

awk (j'utiliserai awk par la suite, mais c'est de gawk que l'on parle) est un langage interprété, installé par défaut sur les distributions linux (au moins rh et mandrake) dans le répertoire /bin. Il permet de traiter des fichiers de données structurées.
Un programme awk est une suite de blocs de code compris entre {} (c'est pas très original) qui sont appliqués aux lignes (par défaut) de données du fichier si un "modèle" est vérifié. La page de manuel nous donne les différents types de modèles utilisables :

BEGIN Le bloc qui suit est exécuté au début du programme, une seule fois.
END Le bloc qui suit est exécuté à la fin du programme, une seule fois.
/expression régulière/ Le bloc qui suit est exécuté si les données en cours de traitement correspondent à l'expression régulière. Si vous ne connaissez pas les expressions régulières, lisez la page man egrep.
Un exemple : /^#/ Permet de selectionner les lignes commençant par un #.
expression relationnelle Le bloc qui suit est exécuté si l'expression est vraie.
Un exemple : maVariable==5
modèle && modèle Le bloc qui suit est exécuté si les deux modèles sont vérifiés.
modèle || modèle Le bloc qui suit est exécuté si au moins un des modèles est vérifié.
modèle0 ? modèle1 : modèle2 Le bloc qui suit est exécuté si les modèles 0 et 1 sont vérifiés où si le modèle2 uniquement est vérifié.
(modèle) Permet de grouper les modèles.
Un exemple : (modèle0 || modèle1) && modèle2
! modèle Le bloc qui suit est exécuté si le modèle n'est pas vérifié.
modèle1, modèle2 Le bloc qui suit est exécuté pour la partie des données en cours commençant par modèle1 et finissant par modèle2.
Un exemple : /\/\*/, /\*\// pour afficher les commentaires multilignes

Premiers pas

Les enregistrements

Pour awk, les blocs de données lues sont séparés par un caractère contenu dans la variable RS (Record Separator, retour à la ligne par défaut). On peut accéder à l'intégralité du bloc grâce à la variable $0. C'est ce qu'on appelle un enregistrement.

Un exemple : Tapez awk '{ print $0 }' un_fichier
Passionant, non ? vous venez de réinventer la commande cat ;-) !
Un autre exemple : Tapez awk '/\/\*/, /\*\// { print $0 }' un_fichier_c_avec_des_commentaires
C'est déjà plus sympa, hein ?
Un dernier exemple : Tapez awk '! (/^ *#/ || /^$/) { print $0 }' un_fichier_conf
Vous n'aviez jamais vu votre fichier de config d'Apache comme ça, si ?

Les champs

Ah, la nature ... Courir nus dans les champs en se tenant la main ... NON, je vous arrête tout de suite.
Chaque enregistrement est séparé en champs par un caractère contenu dans la variable FS (Field Separator, un espace par défaut). Les champs sont accessibles par les variables $1, $2, $3, etc ...
Pour afficher le 3ème champ de toutes les lignes de vos données à traiter, vous utilisez print $3.
Un exemple : awk -F ':' '{ print $1 " est " $5 }' /etc/passwd pour afficher la description des utilisateurs de votre machine.

Les variables

Il existe bon nombre de variables prédéfinies en awk. Je vais vous en donner la liste résumée, encore une fois, je n'invente rien et je ne créée rien, je ne fais que vous refaire une lecture de la page de manuel. D'ailleurs pour avoir une liste exhaustive des variables, consultez la.

ARGC Si vous avez fait du c, vous vous en doutez ! Sinon, je vous le dis. Cette variable contient le nombre d'arguments passés au programme. (sans les options passées à awk)
ARGV Un tableau contenant les ARGC paramètres, indéxé de 0 à ARGC-1. Ce sont tous les fichiers à traiter
CONVFMT Le format par défaut pour l'affichage des nombres, "%.6g" par défaut
ENVIRON Un tableau contenant les variables d'environnement. Exemple ENVIRON["PATH"] contient votre path.
FIELDWIDTHS Pour le cas de traitement de données non délimités mais contenant des champs de largeur fixe.
Cette variable est de la forme largeur_champ_1 largeur_champ_2 largeur_champ_3 etc....
FNR Le numéro de l'enregistrement actuellement en cours de traitement.
Un exemple : awk '{print FNR ": " $0}' un_fichier permet d'afficher le fichier avec les numéros de lignes.
Un autre exemple : awk '{print FNR ": " $0}' un_fichier | grep ^45: permet d'afficher la ligne 45 du fichier un_fichier. On fait appel à grep, ce n'est plus du 100% awk mais c'est beau la diversité, non ?
FS Le séparateur de champs. No comment !
IGNORECASE En gros, permet de ne pas prendre en compte la casse lors des comparaisons de chaines de caractères entre elles où avec des expressions régulières. Voir la page de manuel pour les détails.
NF Norme Française ... NON, Number of fields : C'est le nombre de champs de l'enregistrement en cours.
NR Number of Records : Le nombre d'enregistrements traités jusqu'à maintenant.
RS Le séparateur d'enregistrements. No comment !

Bien entendu, vous pouvez également définir les vôtres.

Exemples :
maVar=3 déclare et initialise la variable myVar à la valeur 3.
print maVar affiche le contenu de la variable maVar.
print $maVar affiche le champ n°maVar de l'enregistrement en cours (ici, équivaut à print $3).
print $(maVar-1) affiche le champ n°(maVar-1) de l'enregistrement en cours (ici, équivaut à print $2).
split("toto:tata:titi:tutu", arr, ":") initialise le tableau arr à ("toto","tata",...). On accède ensuite aux valeurs par arr[1], arr[2], ..., arr[4]

Les fonctions

Il y en a de nombreuses :

Les possibilités de formatage importantes à votre disposition sont définies dans la page de manuel de awk.

Et vous pouvez également définir vos fonctions grâce à la syntaxe : function nom_de_fonction(param1, param2, var_locale_1, var_locale_2) {
...
...
}
ou func nom_de_fonction(param1, param2 var_locale_1, var_locale_2) {
...
...
}
La déclaration des variables locales est étrange. Celà vient du fait que awk n'était pas prévu pour permettre la créaton de fonctions. Il a donc été décidé, de les ajouter à la liste des paramètres, mais séparées par des espaces supplémentaires. L'appel de la fonction, lui, ne contient que les paramètres. Exemple : nom_de_fonction(5,2). Les variables locales sont initialisées à chaine vide ou zéro.

Blocs

Merci à Jean-Louis pour m'avoir montré que je n'insistais pas assez sur le fonctionnement des blocs. C'est grâce à lui que cette partie existe

Utilisation des blocs

Prenons un exemple :

#!/usr/bin/awk -f
BEGIN {
print "début du script"
maVariable=5
nbLignes=0
nbCommentaires=0
nbCommentaires2=0
print "maVariable vaut " maVariable
}

( /^#/ || /^$/ ) {
nbCommentaires++
}

{
nbLignes++
printf "%04d:%s\n",FNR,$0
if ( $0 ~ /^#/ || $0 ~ /^$/ ) {
nbCommentaires2++
}
}

END {
print "fin du script maVariable vaut toujours " maVariable
print "Le script a traité " FNR " lignes dont " nbCommentaires "(=" nbCommentaires2 ") lignes vides ou commençant par un #"
}

Explications :

Exécution

# chmod a+x ceScript.awk
# ./ceScript.awk unfichier.shell
début du script
maVariable vaut 5
0001:# Ce script affiche hello world
0002:
0003:echo "hello world"
0004:exit 0
fin du script maVariable vaut toujours 5
Le script a traité 4 lignes dont 2(=2) lignes vides ou commençant par un #

Divers

awk permet, bien sûr l'utilisation de :

Utiliser awk

Par la ligne de commande

Il existe deux façons de se servir de awk en tapant la commande awk suivie d'arguments.
La première consiste à saisir le code à exécuter directement sur la ligne de commande, celà convient parfaitement à de petits scripts. La syntaxe est la suivante : awk -F séparateur_de_champ 'script awk' fichiers_a_traiter, par exemple awk -F ':' '{ print $1 " est " $5 }' /etc/passwd.
La seconde consiste à placer votre script dans un fichier et à l'appeler par la commande awk -f fichier_script fichier_a_traiter vous pouvez définir le séparateur de champ dans votre script, dans la section BEGIN ou le spécifier sur la ligne de commande grâce à l'option -F, comme ci-dessus. L'exemple ci-dessus deviendrait alors awk -f monScript /etc/passwd ou monScript serait : BEGIN { FS = ":"}
{ print $1 " est " $5 }
ou bien awk -F ':' -f monScript2 /etc/passwd avec monScript2 valant { print $1 " est " $5 }
Notez que dans le script on doit saisir FS = ":" et non FS = ':'. Vous êtes prévenus !

Comme d'habitude, pour les autres options de la ligne de commande, je vous renvoie à la page de manuel de awk.

Des scripts exécutables

Cette utilisation a vite quelque chose de rébarbatif quand même, on rend donc les scripts directement exécutables en plaçant en en-tête #!/bin/awk -f (le chemin doit être adapté à l'emplacement de awk). Créons donc un fichier monScript3 dans lequel nous plaçons : #!/bin/awk -f
BEGIN { FS = ":"}
{ print $1 " est " $5 }
Suite à cette saisie, on rend le script exécutable par chmod a+x et on exécute par ./monScript3 /etc/passwd.

A partir de maintenant, j'estime que vous avez les bases nécessaires pour réaliser vos scripts, si awk vous intéresse. Je vais maintenant vous soumettre des exemples qui vous aideront à mieux comprendre les choses qui sont peut être restées obscures lors de cette présentation sommaire. N'oubliez pas que le bon réflexe en cas de problème reste de lire la doc. En cas de gros pépin, postez un message dans le forum de lea-linux, je ne suis jamais loin ou envoyez moi un mail (j'y réponds dès que j'ai le temps).

Des filtres en exemple

Transformer le fichier hosts

Si vous êtes des habitués du site, ce premier exemple vous rapellera peut être quelque chose puisque je l'avais posté dans le forum en réponse à une personne qui souhaitait adapter le format des IP du fichier hosts à ses besoins, xxx.xxx.xxx.xxx.
Voici donc le code du premier filtre exemple :

#!/usr/bin/awk -f
!(/^#/ || /^$/) {
split ($1, array_ip, ".")
printf "%03d.%03d.%03d.%03d\t%s\t%s\n", array_ip[1], array_ip[2], array_ip[3], array_ip[4],$2,$3
}

La première ligne signifie qu'on va chercher awk dans le répertoire /usr/bin/, ce qui est le bon chemin pour ma slack.
La deuxiême ligne signifie qu'on ne s'intéresse qu'aux lignes qui ne commencent pas par un # ni aux lignes vides. En effet en expressions régulières, ^ signifie début de chaîne et $, fin de chaîne.
Par la suite, on découpe le premier paramètre en prenant comme séparateur le ., on stocke le résultat dans un tableau et on affiche les données du tableau en les formattant puis les autres champs (nom_de_machine et nom_de_machine.domaine). Le formatage %03d signifie qu'on affiche des entiers sur trois caractères et que s'il manque des caractères, on précède ceux qui existent avec des 0.
Il faut rendre ce script exécutable : chmod a+x prepare_hosts.awk
Puis, on l'appelle comme ça : ./prepare_hosts.awk /etc/hosts > hosts.prepared pour récupérer le fichier traité dans un fichier hosts.prepared placé dans le répertoire en cours.

Filtre de GéoConcept© vers GRASS(GNU/GPL)

GéoConcept et GRASS sont deux systèmes d'information géographique (je dirais SIG par la suite). GéoConcept permet d'exporter des objets surfaces selon une syntaxe explicitée ci-dessous. Le fichier export de GéoConcept contient tous les objets exportés.

Bien entendu ce format n'est pas du tout celui de GRASS qui est explicité ci-après, j'ai donc du écrire un p'tit filtre. Il n'est pas universel puisqu'on ne sait pas combien de champs se trouvent avant les coordonnées avant de faire l'export dans GéoConcept. Je le donne ici à titre d'exemple et j'y ai fait de grosses découpes, ne l'utilisez pas tel quel. Si vous êtes arrivés ici en faisant une recherche sur les SIG et que ce script vous intéresse, je peux vous l'envoyer par mail.

Format GéoConcept :
//$SYSCOORD {Type:1}
//$UNIT Distance:m
//$FORMAT 1
28878 buffer comm TRITTELING 911561.09 2462669.95 69 0. 1.e-002 ...
28595 buffer comm LONGWY 848138.94 2509824.85 91 45.62 -171.88 ...
...
Soit en clair :

Format GRASS :

14 lignes d'en-tête comprenant des renseignements divers
...
A 69
2462669.95 911561.09
2462669.96 911561.09
...
Soit en clair :

Les bouts intéressants du script BEGIN {
melange = "v.patch input="
exited = 0
getline h1 < "header"
getline h2 < "header"
...
}

!/\/\// {
print h1 > ENVIRON["LOCATION"] "/dig_ascii/" $1
print h2 > ENVIRON["LOCATION"] "/dig_ascii/" $1
...

melange = melange $1 ".awkImport,"
...

beginObj = 5
if ( (beginObj + 2 + $(beginObj+2) * 2) < NF) {
nbTrou = $((beginObj+3) + $(beginObj+2) * 2)
}
else {
nbTrou = 0
}

for ( i=0 ; i < nbTrou+1 ; i++) {
actualx = $beginObj
actualy = $(beginObj+1)
print "A " ($(beginObj+2)+2) > ENVIRON["LOCATION"] "/dig_ascii/" $1
print " " actualy " " actualx > ENVIRON["LOCATION"] "/dig_ascii/" $1

for ( j=(beginObj+3) ; j<(beginObj+3)+$(beginObj+2)*2-1 ; j++ ) {
actualx += $j
actualy += $(++j)
print " " actualy " " actualx > ENVIRON["LOCATION"] "/dig_ascii/" $1
}
print " " $(beginObj+1) " " $beginObj > ENVIRON["LOCATION"] "/dig_ascii/" $1
...
}
...
fflush (ENVIRON["LOCATION"] "/dig_ascii/" $1)
close (ENVIRON["LOCATION"] "/dig_ascii/" $1)
...
if ( system("v.support map=" $1 ".awkImport option=build -s threshold=" thresh " >/dev/null")!=0 )
exit (1)
}

END {
print "Patching all files into composite.awkImport"
if ( system (melange " output=composite.awkImport" ) != 0 )
exit (1)
...
for (i=1;i<=FNR;i++) {
getline < ARGV[1]
print i ":" $4 > ENVIRON["LOCATION"] "/dig_cats/" mapName
}
system (effaceTempos " > /dev/null")
fflush ()
...
}

Nous allons ensemble analyser les morceaux intéressants de ce script.

Tout d'abord, je définis quelques variables dans la section BEGIN, qui restent accessibles jusqu'à la fin de l'exécution. Ensuite j'affecte des valeurs aux variables h1 et h2 en lisant les lignes dans un fichier du répertoire courant nommé header.

Pour chaque objet (tout ce qui ne commence pas par //), j'insère un entête GRASS, composé des variables h1, h2, etc... que je range dans un fichier avec le symbole de redirection > dont le chemin est la concaténation de la variable d'environnement LOCATION (ENVIRON["LOCATION"]), de la chaîne "/dig_ascii/" et de la première colonne de la ligne que je suis en train de lire ($1).

J'ajoute à la variable melange une chaine constituée de ma variable $1 et de la chaine ".awkImport"

Je dis que les coordonnées de l'objet commencent à la colonne 5. Ceci me dit (voir explications du format GC ci-dessus) que si il y a des trous, c'est à dire si la somme du double du nombre de points intermédiaires donné en colonne 7 ($(beginObj+2) équivaut à $7 car beginObj vaut 5)+ les cinq colonnes de départ + 2 pour les 2 premières coordonnées absolues est inférieur au nombre total de colonnes de la ligne contenu dans la variable NF, alors leur nombre me serra donné à la colonne suivant ce nombre de colonnes précalculé, d'où le nbTrous = $((beginObj+3) + $(beginObj+2) * 2).

Ensuite la reconstitution des coordonnées absolues et l'inscription dans le fichier utilisé plus haut donne l'occasion de voir la syntaxe des boucles for.

On flushe le buffer du fichier et on le ferme. Au début j'avais oublié, alors, je me suis retrouvé avec BEAUCOUP TROP de fichiers ouverts. Vous me direz, le fichier je l'ai pas ouvert. Bé, oui, c'est le côté expérimentation hasardeuse qui m'a fait comprendre. M'enfin, vous pourrez pas dire : "euh, ben, je comprends pas pourquoi mon linux me dit que le script il doit s'arrêter à cause que j'ai un problème de ressources".
Ceci dit, je peux me tromper aussi. Si j'ai tort envoyez moi un mail.

La ligne suivante nous donne un exemple d'utilisation de la commande system, avec récupération du code de retour et aussi de la commande exit. RAS.

La suite montre l'utilisation de variables telles que FNR, ARGV ainsi que des appels à la fonction système avec redirection et l'utilisation des variables déclarées dans le begin et que l'on a fait évoluer au cours du script.

Le mot de la fin, enfin ;-)

Voilà, j'espère vous avoir fait comprendre ce qu'était awk et à quoi il servait. Bien sûr on peut faire la même chose en Perl, PHP, C, Java. Je sais. Mais utiliser awk plutôt que Perl quand il est adapté c'est un peu comme aller dans un petit hôtel/restaurant pittoresque plutôt que d'aller au Club Med. En plus, awk est très léger et peut être un allié puissant dans les systèmes embarqués.

Nota : Je vous ai généré un pdf du manuel de awk accessible ici (64ko). Petit rappel en passant : la manipulation pour générer des pdf à partir des pages de manuel se trouve sur Léa section Trucs et astuces, rubrique Shell.

a+
Xavier

Haut


Programmation de Script: Une introduction

par Fred, correction et ajouts de Marc

Comment écrire de petits scripts permettant d'automatiser la réalisation de taches répétitives.


Introduction

Vous aurez envie d'écrire un script (petit programme écrit avec un langage simple : shell, perl ou autre) dès que vous aurez tapé dans un terminal quatre fois la même série de commandes et que vous vous apercevrez que vous êtes ammené à le refaire de nombreuses fois.
Un script est une suite d'instructions élémentaires qui sont éxécutées de façon séquencielle (les unes après les autres) par le langage de script.
Dans cet article nous nous limiterons à l'utilisation du shell comme langage, et en particulier à du shell bash. En guise de première introduction, vous pouvez lire ce qui concerne les commandes du shell dans l'article Le Shell et les Commandes. Attention, n'espérez pas que le présent document constitue un manuel complet de programmation ! C'est une courte introduction qui nous l'espérons, vous permettra d'écrire de petits scripts qui vous rendront de précieux services.

Notions de base

Mon premier script.

Pour commencer, il faut savoir qu'un script est un fichier texte standard pouvant être créé par n'importe quel éditeur : vi, emacs, kedit, gnotepad, ou autre. D'autre part, conventionnellement, un script commence par une ligne de commentaire contenant le nom du langage à utiliser pour interpréter ce script, soit dans notre cas : /bin/sh (on parle alors de "script shell"). Donc un script shell élémentaire pourrait être :
#!/bin/sh
Note : "#!" se prononce "she bang", soit "chi-bang".
Évidemment un tel script ne fait rien ! Changeons cela. La commande qui affiche quelque chose à l'écran est echo. Donc pour créer le script bonjour_monde nous pouvons écrire :
#!/bin/sh
echo "Bonjour, Monde !"
echo "un premier script est né."
Comment on l'éxécute ? C'est simple il suffit de faire :
[user@becane user]$ sh bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
C'est pas cool, vous préféreriez taper quelque chose comme :
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
C'est possible si vous avez au préalable rendu votre script exécutable par la commande :
[user@becane user]$ chmod +x bonjour_monde
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
Résumons : un script shell commence par : #!/bin/sh, il contient des commandes du shell et est rendu exécutable par chmod +x.

Quelques conseils concernant les commentaires

Dans un shell-script, est considéré comme un commentaire tout ce qui suit le caractère # et ce, jusqu'à la fin de la ligne.
Usez et abusez des commentaires : lorsque vous relirez un script 6 mois après l'avoir écrit, vous serez bien content de l'avoir documenté. Un programme n'est jamais trop documenté. Par contre, il peut être mal documenté ! Un commentaire est bon lorsqu'il décrit pourquoi on fait quelque chose, pas quand il décrit ce que l'on fait. Exemple :
#!/bin/sh
# pour i parcourant tous les fichiers,
for i in * ; do
# copier le fichier vers .bak
cp $i $i.bak
# fin pour
done
Que fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre :
#!/bin/sh
# on veut faire un copie de tous les fichiers
for i in * ; do
# sous le nom *.bak
cp $i $i.bak
done
Là, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.)

Le passage de paramètres

Un script ne sera, en général, que d'une utilisation marginale si vous ne pouvez pas modifier son comportement d'une manière ou d'une autre. On obtient cet effet en "passant" un (ou plusieurs) paramètre(s) au script via la ligne de commande. Voyons comment faire cela. Soit le script essai01:
#!/bin/sh
echo le paramètre \$1 est \"$1\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$3 est \"$3\"
Que fait-il ? Il affiche, les uns après les autres les trois premiers paramètres du script, donc si l'on tappe :
$ ./essai01 paramètre un
le paramètre $1 est "paramètre"
le paramètre $2 est "un"
le paramètre $3 est ""
$ _
Donc, les variables $1, $2 ... $9 contiennent les "mots" numéro 1, 2 ... 9 de la ligne de commande. Attention : par "mot" on entend ensemble de caractères ne contenant pas de caractères de séparations. Les caractères de séparation sont l'espace, la tabulation, le point virgule.

Vous avez sans doute remarqué que j'ai utilisé les caractères : \$ à la place de $ ainsi que \" à la place de " dans le script. Pour quelle raison ? La raison est simple, si l'on tape : echo "essai" on obtient : essai, si l'on veut obtenir "essai" il faut dire à echo que le caractère " n'indique pas le début d'une chaîne de caractère (comme c'est le comportement par défaut) mais que ce caractère fait partie de la chaîne : on dit que l'on "échappe" ou "protège" le caractère " en tapant \". En "échappant" le caractère \ (par \\) on obtient le caractère \ sans signification particulière. On peut dire que le caractère \ devant un autre lui fait perdre sa signification particulière s'il en a une, ne fait rien si le caractère qui suit \ n'en a pas.

Maintenant, essayons de taper :

$ ./essai01 *
le paramètre $1 est "Mail"
le paramètre $2 est "essai01"
le paramètre $3 est "nsmail"
$ _
(Le résultat doit être sensiblement différent sur votre machine). Que s'est-il passé ? Le shell a remplacé le caractère * par la liste de tous les fichiers non cachés présents dans le répertoire actif. En fait, toutes les substitutions du shell sont possibles ! C'est le shell qui "substitue" aux paramètres des valeurs étendues par les caractères spéciaux : * (toute suite de caractères) ? (un caractère grave quelconque), [dze] (l'un des caractères d, z ou e), [d-z] (les caractères de 'd' à 'z')... Autre exemple :
$ ./essai01 \*
le paramètre $1 est "*"
le paramètre $2 est ""
le paramètre $3 est ""
$ _
Hé oui, on a "échappé" le caractère * donc il a perdu sa signification particulière : il est redevenu un simple *.

C'est bien, me direz vous, mais si je veux utiliser plus de dix paramètres ? Il faut utiliser la commande shift ; à titre d'exemple voici le script essai02 :

#!/bin/sh
echo le paramètre 1 est \"$1\"
shift
echo le paramètre 2 est \"$1\"
shift
echo le paramètre 2 est \"$1\"
shift
echo le paramètre 4 est \"$1\"
shift
echo le paramètre 5 est \"$1\"
shift
echo le paramètre 6 est \"$1\"
shift
echo le paramètre 7 est \"$1\"
shift
echo le paramètre 8 est \"$1\"
shift
echo le paramètre 9 est \"$1\"
shift
echo le paramètre 10 est \"$1\"
shift
echo le paramètre 11 est \"$1\"
Si vous tapez :
$ ./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13
le paramètre 1 est "1"
le paramètre 2 est "2"
le paramètre 2 est "3"
le paramètre 4 est "4"
le paramètre 5 est "5"
le paramètre 6 est "6"
le paramètre 7 est "7"
le paramètre 8 est "8"
le paramètre 9 est "9"
le paramètre 10 est "10"
le paramètre 11 est "11"
$ _
A chaque appel de shift les paramètres sont décalés d'un numéro : le paramètre 2 devient le paramètre 1, 3 devient 2, etc... Évidemment le paramètre 1 est perdu par l'appel de shift : vous devez donc vous en servir avant d'appeler shift (ou le sauvegarder dans une variable).

Les variables

Le passage des paramètres nous a montré l'utilisation de "noms" particuliers : $1, $2 etc. Ce sont les substitutions des variables 1, 2, etc. par leur valeurs. Mais vous pouvez définir et utiliser n'importe quel nom. Attention toutefois à ne pas confondre le nom d'une variable (notée par exemple machin) et son contenu (noté dans cas $machin). Vous connaissez peut-être la variable PATH (attention, le shell différencie les majuscules des minuscules) qui contient la liste des répertoires (séparés par des ":") dans lesquels il doit rechercher les programmes. Si dans un script vous tapez :
1:#!/bin/sh
2:PATH=/bin # PATH contient /bin
3:PATH=PATH:/usr/bin # PATH contient PATH:/usr/bin
4:PATH=/bin # PATH contient /bin
5:PATH=$PATH:/usr/bin # PATH contient /bin:/usr/bin
(Les numéros ne sont là que pour repérer les lignes, il ne faut pas les taper)
La ligne 3 est très certainement une erreur, à gauche du signe "=" il faut une variable (donc un nom sans $) mais à droite de ce même signe il faut une valeur, et la valeur que l'on a mis est "PATH:/usr/bin" : il n'y a aucune substitution à faire.
Par contre la ligne 5 est certainement correcte : à droite du "=" on a mis "$PATH:/usr/bin", la valeur de $PATH étant "/bin", la valeur après substitution par le shell de "$PATH:/usr/bin" est "/bin:/usr/bin". Donc, à la fin de la ligne 5, la valeur de la variable PATH est "/bin:/usr/bin".

Attention : il ne doit y avoir aucun espace de part et d'autre du signe "=".

Résumons : MACHIN est un nom de variable que l'on utilise lorsque l'on a besoin d'un nom de variable (mais pas de son contenu), et $MACHIN est le contenu de la variable MACHIN que l'on utilise lorsque l'on a besoin du contenu de cette variable.

Variables particulières

Il y a un certain nombre de variables particulières, en voici quelques unes : Il y en a d'autres, moins utilisées : allez voir la man page de bash.

Saisir la valeur d'une variable

Les paramètres permettent à l'utilisateur d'agir sur le déroulement du script avant son exécution. Mais il est aussi souvent intéressant de pouvoir agir sur le déroulement du script lors de son exécution, c'est ce que permet la commande : read nom_variable. Dans cette commande vous pouvez bien sûr remplacer nom_variable par le nom de variable qui vous convient le mieux. Voici un exemple simple.
#!/bin/sh
echo -n "Entrez votre prénom : "
read prenom
echo -n "Entrez votre nom de login : "
read nomlogin
echo "Le nom de login de $prenom est $nomlogin."
Ce script se déroule ainsi :
./essai02bis
Entrez votre prénom : Marc
Entrez votre nom de login : spoutnik
Le nom de login de Marc est spoutnik.
Lors du déroulement du script vous devez valider vos entrées en appuyant sur la touche "Entrée".

Arithmétique

Vous vous doutez bien qu'il est possible de faire des calculs avec le shell. En fait, le shell ne "sait" faire que des calculs sur les nombres entiers (ceux qui n'ont pas de virgules ;-). Pour faire un calcul il faut encadrer celui-ci de : $(( un calcul )) ou $[ un calcul ]. Exemple, le script essai03 :
#!/bin/sh
echo 2+3*5 = $((2+3*5))
MACHIN=12
echo MACHIN*4 = $[$MACHIN*4]
Affichera :
$ sh essai03
2+3*5 = 17
MACHIN*4 = 48
Vous remarquerez que le shell respecte les priorités mathématiques habituelles (il fait les multiplications avant les additions !). L'opérateur puissance est "**" (ie : 2 puissance 5 s'écrit : 2**5). On peut utiliser des parenthèses pour modifier l'ordre des calculs.

Les instructions de contrôle de scripts

Les instructions de contrôle du shell permettent de modifier l'exécution purement séquencielle d'un script. Jusqu'à maintenant, les scripts que nous avons créés n'étaient pas très complexes. Ils ne pouvaient de toute façon pas l'être car nous ne pouvions pas modifier l'ordre des instructions, ni en répéter.

L'exécution conditionnelle

Lorsque vous programmerez des scripts, vous voudrez que vos scripts fassent une chose si une certaine condition est remplie et autre chose si elle ne l'est pas. La construction de bash qui permet cela est le fameux test : if then else fi. Sa syntaxe est la suivante (la partie else... en italique est optionnelle) :
if <test> ; then
<instruction 1>
<instruction 2>
...
<instruction n>
else
<instruction n+1>
...
<instruction n+p>
fi
Il faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable ? dont la valeur est, rappelons le : "$?".
Pour le shell une valeur nulle est synonyme de VRAI et une valeur non nulle est synonyme de FAUX (ceci parce que, en général les programmes renvoie zéro quand tout c'est bien passé et un code d'erreur (numéro non nul) quand il s'en est produit une).

Il existe deux programmes particuliers : false et true. true renvoie toujours 0 et false renvoie toujours 1. Sachant cela, voyons ce que fait le programme suivant :

#!/bin/sh
if true ; then
echo Le premier test est VRAI($?)
else
echo Le premier test est FAUX($?)
fi

if false ; then
echo Le second test est VRAI($?)
else
echo Le second test est FAUX($?)
fi

Affichera :
$ ./test
Le premier test est VRAI(0)
Le second test est FAUX(1)
$ _
On peut donc conclure que l'instruction if ... then ... else ... fi, fonctionne de la manière suivante : si (if en anglais) le test est VRAI(0) alors (then en anglais) le bloc d'instructions compris entre le then et le else (ou le fi en l'absence de else) est exécuté, sinon (else en anglais) le test est FAUX(différent de 0)) et on exécute le bloc d'instructions compris entre le else et le fi si ce bloc existe.

Bon, évidemment, des tests de cet ordre ne paraissent pas très utiles. Voyons maintenant de vrais tests.

Les tests

Un test, nous l'avons vu, n'est rien de plus qu'une commande standard. Une des commandes standard est 'test', sa syntaxe est un peu complexe, je vais la décrire avec des exemples. Pour plus d'information faites : man test.

On peut aussi combiner deux tests par des opérations logiques : 'ou' correspond à -o ('o' comme or), 'et' correspond à -a ('a' comme and) (à nouveau allez voir la man page), exemple :

test -x /bin/sh -a -d /etc
Cette instruction teste l'existence de l'éxécutable /bin/sh (-x /bin/sh) et (-a) la présence d'un répertoire /etc (-d /etc).

On peut remplacer la commande test <un test> par [ <un test> ] qui est plus lisible, exemple :

if [ -x /bin/sh ] ; then # ('x' comme executable)
echo /bin/sh est exécutable. C\'est bien.
else
echo /bin/sh n\'est pas exécutable.
echo Votre système n\'est pas normal.
fi

Toujours avec les crochets de test, si vous n'avez qu'une seule chose à faire en fonction du résultat d'un test, alors vous pouvez utiliser la syntaxe suivante :

[ -x /bin/sh ] && echo /bin/sh est exécutable.
ou encore :
[ -x /bin/sh ] || echo /bin/sh n\'est pas exécutable.

L'affichage du message est effectué, dans le premier cas que si le test est vrai et dans le second cas, que si le test est faux. Dans l'exemple on teste si /bin/sh est un fichier exécutable.
Cela allége le script sans pour autant le rendre illisible, si cette syntaxe est utilisée à bon essient.


Mais il n'y a pas que la commande test qui peut être employée. Par exemple, la commande grep renvoie 0 quand la recherche a réussi et 1 quand la recherche a échoué.
Par exemple :

if grep -E "^frederic:" /etc/passwd > /dev/null ; then
echo L\'utilisateur frederic existe.
else
echo L'utilisateur frederic n\'existe pas.
fi
Cette série d'instruction teste la présence de l'utilisateur frederic dans le fichier /etc/passwd. Vous remarquerez que l'on a fait suivre la commande grep d'une redirection vers /dev/null pour que le résultat de cette commande ne soit pas affiché : c'est une utilisation classique. Ceci explique aussi l'expression : "Ils sont tellement intéressants, tes mails, que je les envoie vers /dev/null" ;-).

Faire quelque chose de différent suivant la valeur d'une variable

L'instruction case ... esac permet de modifier le déroulement du script selon la valeur d'un paramètre ou d'une variable. On l'utilise le plus souvent quand les valeurs possibles sont en nombre restreint et peuvent être prévues. Les imprévus peuvent alors être représentés par le signe *. Demandons par exemple à l'utilisateur s'il souhaite afficher ou non les fichiers cachés du répertoire en cours.
#!/bin/sh
# pose la question et récupère la réponse
echo "Le contenu du répertoire courant va être affiché."
echo -n "Souhaitez-vous afficher aussi les fichiers cachés (oui/non) : "
read reponse
# agit selon la réponse
case $reponse in
oui)
clear
ls -a;;
non)
ls;;
*)
echo "Erreur, vous deviez répondre par oui ou par non.";;
esac
Seules les réponses "oui" et "non" sont réellement attendues dans ce script, toute autre réponse engendrera le message d'erreur. On notera qu'ici l'écran est effacé avant l'affichage dans le cas d'une réponse positive, mais pas dans celui d'une réponse négative. Lorsque vous utilisez l'instruction case ... esac, faites bien attention de ne pas oublier les doubles points-virgules terminant les instructions de chacun des cas envisagés.

Faire la même chose pour tous les éléments d'une liste

Lorsqu'on programme, on est souvent amené à faire la même chose pour tous les élément d'une liste. Dans un shell script, il est bien évidemment possible de ne pas réécrire dix fois la même chose. On dira que l'on fait une boucle. L'instruction qui réalise une boucle est
for <variable> in <liste de valeurs pour la variable> ; do
<instruction 1>
...
<instruction n>
done
Voyons comment ça fonctionne. Supposons que nous souhaitions renommer tous nos fichiers *.tar.gz en *.tar.gz.old, nous taperons le script suivant :
#!/bin/sh
# I prend chacune des valeurs possibles correspondant
# au motif : *.tar.gz
for I in *.tar.gz ; do
# tous les fichiers $I sont renommés $I.old
echo "$I -> $I.old"
mv $I $I.old
# on finit notre boucle
done
Simple, non ? Un exemple plus complexe ? Supposons que nous voulions parcourir tous les sous-répertoires du répertoire courant pour faire cette même manipulation. Nous pourrons taper :
1:#!/bin/sh
2:for REP in `find -type d` ; do
3: for FICH in $REP/*.tar.gz ; do
4: if [ -f $FICH ] ; then
5: mv $FICH $FICH.old
6: else
7: echo On ne renomme pas $FICH car ce n\'est pas un répertoire
8: fi
9: done
10:done
Explications : dans le premier 'for', on a précisé comme liste : `find -type d` (attention au sens des apostrophes, sur un clavier azerty français, on obtient ce symbole en appuyant sur ALTGR+é, ce ne sont pas des simples quotes ').
Lorsque l'on tape une commande entre apostrophes inverses, le shell exécute d'abord cette commande, et remplace l'expression entre apostrophes inverses par la sortie standard de cette commande (ce qu'elle affiche à l'écran).
Donc, dans le cas qui nous intéresse, la liste est le résultat de la commande find -type d, c'est à dire la liste de tous les sous-répertoires du répertoire courant.
Ainsi, en ligne 2, on fait prendre à la variable REP le nom de chacun des sous-répertoires du répertoire courant, puis (en ligne 3) on fait prendre à la variable FICH le nom de chacun des fichiers .tar.gz de $REP (un des sous-répertoires), puis si $FICH est un fichier, on le renomme, sinon on affiche un avertissement.

Remarque : ce n'est pas le même fonctionnement que la boucle for d'autres langage (le pascal, le C ou le basic par exemple).

Faire une même chose tant qu'un certaine condition est remplie

Pour faire une certaine chose tant qu'une condition est remplie, on utilise un autre type de boucle :
while <un test> ; do
<instruction 1>
...
<instruction n>
done
Supposons, par exemple que vous souhaitiez afficher les 100 premiers nombres (pour une obscure raison), alors vous taperez :
i=0
while [ $i -lt 100 ] ; do
echo $i
i=$[$i+1]
done
Remarque : -lt signifie "lesser than" ou "plus petit que" (et -gt signifie "plus grand", ou "greater than").

Ici, on va afficher le contenu de i et lui ajouter 1 tant que i sera (-lt) plus petit que 100. Remarquez que 100 ne s'affiche pas, car -lt est "plus petit", mais pas "plus petit ou égal" (dans ce cas, utilisez -le et -ge pour "plus grand ou égal").

Refaire à un autre endroit la même chose

Souvent, vous voudrez refaire ce que vous venez de taper autre part dans votre script. Dans ce cas il est inutile de retaper la même chose, préférez utiliser l'instruction function qui permet de réutiliser une portion de script (on dit : une "fonction"). Voyons un exemple :
#!/bin/sh
function addpath ()
{
if echo $PATH | grep -v $1 >/dev/null; then
PATH=$PATH:$1;
fi;
PATH=`echo $PATH|sed s/::/:/g`
}

addpath /opt/apps/bin
addpath /opt/office52/program
addpath /opt/gnome/bin

Au début, nous avons défini une fonction nommée addpath dont le but est d'ajouter le premier argument ($1) de la fonction addpath à la varaible PATH si ce premier argument n'est pas déjà présent (grep -v $1) dans la variable PATH, ainsi que supprimer les chemins vides (sed s/::/:/g) de PATH.
Ensuite, nous exécutons cette fonction pour trois arguments : /opt/apps/bin, /opt/office52/bin et /opt/gnome/bin.

En fait, une fonction est seulement un script écrit à l'intérieur d'un script. Les fonctions permettent surtout de ne pas multiplier les petits scripts, ainsi que de partager des variables sans se préoccuper de la clause export mais cela constitue une utilisation avancée du shell, nous n'irons pas plus loin dans cet article.

Remarque : le mot function peut être omis, mais son utilisation facilite la lecture du script.

Autres types de répétitions.

Il existe d'autres types de répétitions, mais nous ne nous en occuperons pas dans cet article, je vous conseille la lecture, forcément profitable, de la "man page" de bash (man bash).

À vous de jouer !

Haut


Dialog avec le bash

par Marc

T'as d'belles boîtes t'sais ?

Introduction

Le programme dialog comporte de nombreuses "boîtes" et options qui permettent d'améliorer la présentation et donc la convivialité des scripts bash. De grandes distributions comme Debian et Slackware (célèbres entre toutes pour leur aspect user friendly...) l'utilisent d'ailleurs dans leurs scripts d'installation et de configuration. Vous-mêmes, vous ne pourrez bien utiliser dialog que si vous possédez déjà quelques connaissances des scripts bash. Si tel n'est pas le cas, consultez en priorité l'article Introduction aux scripts

Cette page de présentation de dialog n'a pas la structure d'un article traditionnel mais repose avant tout sur un script: demo_dialog.sh. Ce script a été écrit et commenté avec un souci de simplicité et de clarté. A chacune des "boîtes" de dialog qui y est présentée est associée une fonction afin que vous... Mais tout ceci est justement expliqué dans les commentaires placés au début du script que vous trouverez ci-dessous et dont voici le menu principal.

demo_dialog.jpg

Si vous le souhaitez, continuez donc à lire cette page tout en faisant tourner le script dans un (x)terminal. Pour ce faire, téléchargez demo_dialog.txt ; renommez-le en demo_dialog.sh (mv demo_dialog.txt demo_dialog.sh), rendez-le executable (chmod +x demo_dialog.sh) et enfin lancez-le (./demo_dialog.sh).

Démo dialog

#!/bin/sh
# Script de présentation de dialog
# demo_dialog.sh copyleft spi.marc 2004

# Pour découvrir dialog par la pratique :
# faites tourner ce script, examinez son code.

# Différentes boîtes de dialog sont ici présentées dans
# un contexte minimal. A chacune de ces boîtes correspond
# une fonction ; elles apparaissent dans l'ordre suivant.
# [message d'introduction à dialog (fonction intr)]
# Définition
# - d'une boîte d'information (fonction info)
# - d'une boîte de message (fonction mess)
# - d'une boîte de sélection de fichier (fonction fsel)
# - d'une boîte de fichier texte (fonction text)
# - d'une boîte de barre d'avancement (fonction gaug)
# - d'une boîte oui non (fonction yesn)
# - d'une boîte d'entrée de valeur (fonction inpu)
# - d'une boîte de mot de passe (fonction pass)
# - d'une boîte d'heure (fonction time)
# - d'une boîte de menu (fonction menu)
# [message sur les boîtes de cases à cocher (fonction mche)]
# - d'une boîte de cases à cocher (fonction chec)

# # # Début du script proprement dit # # #

# Définition d'un fichier temporaire
# Il sert à conserver les sorties de dialog qui sont normalement
# redirigées vers la sortie d'erreur (2). trap sert à être propre.
touch /tmp/dialogtmp && FICHTMP=/tmp/dialogtmp
trap "rm -f $FICHTMP" 0 1 2 3 5 15

# Définitions des différentes fonctions
# Message d'introduction à dialog
# (boîtes de message voyez plutôt la fonction mess)

function intr ()
{
dialog --backtitle "Présentation de dialog" --title "Introduction" \
--ok-label "Suite" --msgbox "
Dialog vous permet d'améliorer la présentation de vos
scripts bash en mode texte. Il comporte de nombreuses
boîtes et options, vous en découvrirez quelques unes
en utilisant ce script (console 80x25 recommandée).

En mode graphique vous pouvez utiliser xdialog dont la
syntaxe est proche de celle de dialog." 12 60

dialog --backtitle "Présentation de dialog" --title "Introduction (suite)" \
--msgbox "
Une instruction dialog possède la syntaxe suivante:
dialog --options communes --options de boîte

Par exemple --title est une option commune que l'on
peut utiliser avec toutes les boîtes. Alors que
--info est une option spécifique (de boîte info)." 12 60
}

# Définition d'une boîte d'information
function info ()
{
dialog --backtitle "Présentation de dialog" --title "Boîte d'information" \
--sleep 12 --infobox "
Ceci est une boîte d'information.
Sa syntaxe est --infobox text height width
Elle ne comporte pas de bouton par défaut.
Ici l'option --sleep 12 fixe son temps de vie à 12 s.

Pour la tester vous pouvez entrer dans une console:
dialog --sleep 4 --infobox Information 8 40" 12 60

}

# Définition d'une boîte de message
function mess ()
{
dialog --backtitle "Présentation de dialog" --title "Boîte de message" \
--msgbox "
Ceci est une boîte de message.
Sa syntaxe est --msgbox text height width
Elle ne comporte qu'un seul bouton par défaut.

Pour la tester vous pouvez entrer dans une console:
dialog --msgbox Message 8 40" 12 60

}

# Définition d'une boîte de sélection de fichier
function fsel ()
{
# message sur les boîtes de sélection de fichier (boîte de message)
dialog --backtitle "Présentation de dialog" --title "Les boîtes de sélection de fichier" \
--ok-label "Suite" --msgbox "
Une boîte de sélection de fichier permet ...
de sélectionner un fichier.
Sa syntaxe est --fselect filepath height width
Pour l'afficher vous pouvez entrer dans une console:
dialog --fselect $HOME/ 12 60

Voyez aussi la boîte suivante de ce script." 12 60
# boîte de sélection de fichier proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte de sélection de fichier" \
--ok-label "Valider" --fselect $HOME/ 8 60 2> $FICHTMP

# retour d'information (boîte d'info)
# 0 est le code retour du bouton Valider
# seul celui-ci permet ici de sélectionner un fichier.
if [ $? = 0 ]
then INFO="Le fichier sélectionné est `cat $FICHTMP`"
else INFO="Vous n'avez pas sélectionné de fichier!"
fi
dialog --backtitle "Présentation de dialog" --title "Votre sélection" \
--sleep 2 --infobox "
$INFO" 8 40
}

# Définition d'une boîte de fichier texte
function text ()
{
# message sur les boîtes de fichier texte (boîte de message)
dialog --backtitle "Présentation de dialog" --title "Les boîtes texte" \
--ok-label "Suite" --msgbox "
Une boîte texte permet d'afficher le contenu d'un
fichier texte passé en paramètre.
Sa syntaxe est --textbox file height width
Pour l'essayer vous pouvez entrer dans une console:
dialog --textbox /etc/inittab 18 60

La boîte texte suivante va afficher ce script.
Utilisez les 4 flèches pour vous déplacer." 14 60
# boîte de fichier texte proprement dite
# $0 est la variable correspondant au script
dialog --backtitle "Présentation de dialog" --title "Boîte de fichier texte" \
--textbox $0 18 60

}

# Définition d'une boîte de barre d'avancement
# La jauge avance par la redirection |
# de la sortie du sous shell ( ) qui la précède.
function gaug ()
{
(for i in `seq 0 10 100` ; do echo $i ; sleep 1 ; done) | \
dialog --backtitle "Présentation de dialog" --title "Boîte de barre d'avancement" \
--gauge "
Ceci est une boîte de barre d'avancement.
Sa syntaxe est --gauge text height width [percent]
Sa progression vient du sous shell () qui la précède.

Vous avez 10 secondes pour lire ce message.
" 12 60 0

}

# Définition d'une boîte oui non
function yesn ()
{
# boîte oui non proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte oui non" \
--yesno "
Ceci est une boîte oui non.
Elle sert bien sûr à poser une question fermée.
Sa syntaxe est --yesno text height width
Pour l'essayer vous pouvez entrer dans une console:
dialog --yesno Question 8 40

Souhaitez-vous quitter ce script ? " 12 60
# traitement de la réponse
# O est le code retour du bouton Oui
# ici Oui arrête le script
# toute autre action (Non, Esc, Ctrl-C) le poursuit
if [ $? = 0 ]
then exit 0
else
# boîte d'info
dialog --backtitle "Présentation de dialog" --title "Remerciements" \
--sleep 4 --infobox "
L'auteur de ce script vous trouve
de ce fait bien sympathique." 8 40
fi
}

# Définition d'une boîte d'entrée de valeur
function inpu ()
{
# boîte d'entrée de valeur proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte d'entrée" \
--inputbox "
Ceci est une boîte d'entrée.
Sa syntaxe est --inputbox text height width [init]
Ici l'option init est utilisée et contient user
Pour l'afficher vous pouvez entrer dans une console:
dialog --inputbox Entrée 8 40

Entrez votre nom de login:" 14 60 user 2> $FICHTMP
# retour d'information (boîte d'info)
# 0 est le code retour du bouton Accepter
# ici seul celui-ci attribue un nom de login.
if [ $? = 0 ]
then INFO="Votre nom de login est `cat $FICHTMP`"
else INFO="Vous n'avez pas de nom de login!"
fi
dialog --backtitle "Présentation de dialog" --title "Votre login" \
--sleep 2 --infobox "
$INFO" 8 40
}

# Définition d'une boîte de mot de passe
function pass ()
{
# boîte de mot de passe proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte de mot de passe" \
--insecure --passwordbox "
Ceci est une boîte de mot de passe.
Sa syntaxe est --passwordbox text height width [init]
Ici l'option --insecure est utilisée pour avoir des *
Pour la tester vous pouvez entrer dans une console:
dialog --passwordbox Password 8 40

Entrez un mot de passe fictif (il sera affiché):" 14 60 2> $FICHTMP
# retour d'information (boîte d'info)
# pour la démo, à éviter en temps normal !
# 0 est le code retour du bouton Accepter
# ici seul celui-ci attribue un mot de passe.
if [ $? = 0 ]
then INFO="Le mot de passe choisi est `cat $FICHTMP`"
else INFO="Vous n'avez pas de mot de passe!"
fi
dialog --backtitle "Présentation de dialog" --title "Votre mot de passe" \
--sleep 2 --infobox "
$INFO" 8 40
}

# Définition d'une boîte d'heure
function time ()
{
# boîte d'heure proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte d'heure" \
--timebox "
Ceci est une boîte d'heure. Sa syntaxe est
--timebox text height width [hour minute second]
Pour l'afficher vous pouvez entrer dans une console:
dialog --timebox Heure 4 40

Quelle est votre heure locale ?" 10 60 2> $FICHTMP
# retour d'information (boîte d'info)
# 0 est le code retour du bouton Accepter
# seul celui-ci permet de définir une heure différente.
if [ $? = 0 ]
then INFO="Votre heure locale est `cat $FICHTMP`"
else INFO="Vous n'avez pas défini d'heure.
L'heure par défaut est `date +%X`"
fi
dialog --backtitle "Présentation de dialog" --title "Heure locale" \
--sleep 2 --infobox "
$INFO" 8 40
}

# Définition d'une boîte de menu
function menu ()
{
# boîte de menu proprement dite
dialog --backtitle "Présentation de dialog" --title "Boîte de menu" \
--menu "
Ceci est une boîte de menu. Sa syntaxe est
--menu text height width menu-height [tag item] ...
Elle permet de faire un choix parmi une liste.

Choisissez une des entrées proposées:" 18 60 6 \
"Continuer" "Continuer selon les choix déjà faits" \
"Introduction" "Relire l'intro à dialog puis continuer" \
"Arrêter" "Et au revoir..." 2> $FICHTMP

# traitement de la réponse
if [ $? = 0 ]
then
for i in `cat $FICHTMP`
do
case $i in
# Continuer est par défaut
Introduction) intr ;;
Arrêter) exit 0 ;;
esac
done
fi
}

# Message sur les boîtes de cases à cocher
# (boîtes de message voyez plutôt la fonction mess)

function mche ()
{
dialog --backtitle "Présentation de dialog" --title "Les boîtes de cases à cocher" \
--ok-label "Suite" --msgbox "
Une boîte de cases à cocher permet de proposer un menu
à choix multiples. Sa syntaxe est --checklist
text height width list-height [tag item status] ...

Le menu principal de ce script est une boîte de ce
type. Vous la retrouverez en quittant cet écran." 12 60
}

# Définition d'une boîte de cases à cocher
# Menu principal de choix des boîtes du script
function chec ()
{
# boîte de cases à cocher proprement dite
dialog --backtitle "Présentation de dialog" --title "Liste des boîtes" \
--ok-label "Valider" --cancel-label "Quitter" \
--checklist "
Cochez les boîtes dont vous souhaitez une présentation." 18 60 10 \
"intr" "Introduction à dialog" off \
"info" "Boîte d'information" off \
"mess" "Boîte de message" off \
"fsel" "Boîte de sélection de fichier" off \
"text" "Boîte de fichier texte" off \
"gaug" "Boîte de barre d'avancement" off \
"yesn" "Boîte oui non" off \
"inpu" "Boîte d'entrée de valeur" off \
"pass" "Boîte de mot de passe" off \
"time" "Boîte d'heure" off \
"menu" "Boîte de menu" off \
"mche" "Boîte de cases à cocher (message sur)" off 2> $FICHTMP

# traitement de la réponse
# 0 est le code retour du bouton Valider
# ici seul le bouton Valider permet de continuer
# tout autre action (Quitter, Esc, Ctrl-C) arrête le script.
if [ $? = 0 ]
then
for i in `cat $FICHTMP`
do
case $i in
\"intr\") intr ;;
\"info\") info ;;
\"mess\") mess ;;
\"fsel\") fsel ;;
\"text\") text ;;
\"gaug\") gaug ;;
\"yesn\") yesn ;;
\"inpu\") inpu ;;
\"pass\") pass ;;
\"time\") time ;;
\"menu\") menu ;;
\"mche\") mche ;;
esac
done
else exit 0
fi
}

# Fin des définitions des fonctions
# Boucle d'appel du menu principal à l'infini

while :
do chec
done

# dialog offre de nombreuses autres possibilités.
# A vous maintenant de les découvrir au travers
# par exemple de sa page man :o)

# # # Fin du script # # #

Conclusion

Le programme dialog offre bien d'autres possibilités qui ne sont ni présentées dans cette page ni exploitées dans le script. On ne peut faire simple et compliqué à la fois. La plupart des boîtes ont été mises dans un contexte minimum que vous pouvez enrichir. Ainsi par exemple certains boutons et codes retour n'ont pas été utilisés. En voici la liste car ils pourraient cependant vous être utiles. Les boutons pouvant être renommés à volonté, les labels attribués peuvent donc varier selon les scripts.

Bouton Yes ou OK (Oui ou Accepter) : 0
Bouton No ou Cancel (Non ou Annuler) : 1
Bouton Help (Aide) : 2
Bouton Extra : 3
Touche Esc : 255
Erreurs dans dialog : -1

Maintenant que vous avez fait connaissance avec dialog, et avez certainement compris le principe général de son fonctionnement, vous pourrez tirer plus de profit de sa page man dont voici une traduction française.

Merci d'avoir lu cet article. Si en utilisant le script vous y trouviez quelques bugs, merci de me mailer afin de m'en avertir. Si vous avez des idées d'améliorations qui s'accordent avec la simplicité et la lisibilité souhaitées de l'ensemble, merci aussi de m'en faire part... en joignant autant que possible le code correspondant à celles-ci.

Merci à Léa d'avoir accepté de publier cet article.

Haut


SUID Scripts

par Xavier GARREAU

Cet article fait suite à une question qui avait été débattue sur la liste lea_aide@club.voila.fr au premier semestre 2001. Comme ce sujet m'avait passionné et qu'il pourrait intéresser d'autres personnes, j'ai écrit cet article.
Afin d'éviter une polémique, je dis tout de suite que la réponse apportée ci-dessous m'avait été inspirée par la lecture de l'article "Eviter les failles dès le développement de vos applications" paru dans le Linuxmagazine-france de Décembre 2000. Ceci dit, le problème est traité ici dans une optique différente qui est de permettre à un administrateur de permettre aux utilisateurs de lancer quelques scripts choisis en temps que root.


Introduction

Qu'est ce que j'appelle un SUID Script ? C'est un script que vous souhaiteriez pouvoir exécuter en temps que simple utilisateur mais qui ferait des choses uniquement permises au root.
J'en vois qui sourient en me prenant pour un débile se jeter sur leur shell pour me prouver que je suis un tocard qui ne connait pas chmod 4755. Lisez donc encore quelques lignes avant de m'envoyer un mail d'insultes.

Un échec

Voyons si cet article s'adresse à vous !
Imaginons que vous soyez root vous voulez permettre à un simple utilisateur de lire le contenu de votre répertoire personnel, dans un souci de transparence, pour prouver qu'ils n'y trouveront pas de photos pornographiques. Vous écrivez donc un script qui permet d'afficher le contenu de votre répertoire personnel ( je présuppose chez vous des bases de shell ;-) ) :

[root@ZAZOU /root]# cat > voir_rep_root
#!/bin/sh
echo "Contenu du répertoire de" `whoami`
# ou echo "Contenu du répertoire de" $(whoami)
ls -a /root
< Ctrl+D >
et vous le rendez suid root et vous le placez dans /usr/bin pour que tous les utilisateurs puissent l'exécuter en temps que vous ! :
[root@ZAZOU /root]# chmod 4755 voir_rep_root
[root@ZAZOU /root]# mv voir_rep_root /usr/bin/
(Les plus assidus d'entre vous constaterons que mon PC ne s'appelle plus Rooty mais là n'est pas la question)
Vous testez que ça marche pour vous puis en temps qu'utilisateur normal (l'utilisateur xavier par exemple)
[root@ZAZOU /root]# voir_rep_root
Contenu du répertoire de root
.            .bash_history  .cshrc          .toprc  Mail
..           .bash_logout   .mysql_history  .vimrc
.Xauthority  .bash_profile  .parsecrc       .wmrc
.Xdefaults   .bashrc        .tcshrc         .zshrc
[root@ZAZOU /root]# su xavier
[xavier@ZAZOU /root]$ voir_rep_root
Contenu du répertoire de xavier
ls: /root: Permission non accordée

Vous avez compris le problème ça y est ? Votre script est exécutable, appartient au root, a le SUID bit à 1 mais n'en tient pas compte.

Scènette de fin de partie
"- La solution c'est chmod 4755 /bin/bash
- Qui a dit ça ? Bill ? A la porte, tout de suite !"

Bill ? Porte ? Je vous laisse méditer là dessus et passe à l'étape suivante.

En C, ça ne marche pas non plus (au début) !

Vous vous dites alors : "Je suis très intelligent, je vais l'avoir en finesse...". Hé hé hé ! Je rigole parce que c'est ce que je me suis dit moi aussi ...
Si sur les programmes ça marche, on est tentés de se dire qu'on va écrire un programme qui appelle le script. Allons y, créons lanceur_de_script.c :

#include <unistd.h>
#include <errno.h>
#include <stdio.h>

extern char **environ;
extern int errno;

int main (int argc, char **argv) {
  execve("/usr/bin/voir_rep_root", NULL, environ);
  printf ("Error : %d\n", errno);
  return errno;
}
( Parenthèse : A ceux qui seraient tentés de dire : "Oui mais pourquoi on met return errno ?", je réponds d'aller lire le man execve où ils pourront constater que si execve ne rencontre pas d'erreur, alors il ne renvoie rien puisque l'image du programme est TOTALEMENT remplacée par celle du programme appelé (ce programme étant l'interpréteur dans le cas d'un script). )
On est content d'avoir fait ce beau programme. Alors on le compile, on le rend exécutable par tout le monde, on met le SUID bit à 1, on le déplace dans /usr/bin et on refait le test de tout à l'heure.
[root@ZAZOU /root]# gcc -o lanceur_de_script lanceur_de_script.c
[root@ZAZOU /root]# chmod 4111 lanceur_de_script
[root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/
[root@ZAZOU /root]# lanceur_de_script
Contenu du répertoire de root
.            .bash_history  .cshrc          .toprc  Mail
..           .bash_logout   .mysql_history  .vimrc  lanceur_de_script.c
.Xauthority  .bash_profile  .parsecrc       .wmrc
.Xdefaults   .bashrc        .tcshrc         .zshrc
[root@ZAZOU /root]# su xavier
[xavier@ZAZOU /root]$ lanceur_de_script
Contenu du répertoire de xavier
ls: /root: Permission non accordée

Le début de la compréhension

Oui, pour comprendre, rendez vous à la fin de l'article ;-) ... Pour suivre le raisonnement, ajoutez dans le programme une ligne :
printf ("UID %d - EUID %d\n", getuid(), geteuid()); comme ci-dessous :

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>

extern char **environ;
extern int errno;

int main (int argc, char **argv) {
  printf ("UID %d - EUID %d\n", getuid(), geteuid());
  execve("/usr/bin/voir_rep_root", NULL, environ);
  printf ("Error : %d\n", errno);
  return errno;
}
Celà vous donne un début de réponse lors de l'exécution (non ?) :
[root@ZAZOU /root]# chmod 4111 lanceur_de_script
[root@ZAZOU /root]# mv -f lanceur_de_script /usr/bin/
[root@ZAZOU /root]# su xavier
[xavier@ZAZOU /root]$ lanceur_de_script
UID 501 - EUID 0
Contenu du répertoire de xavier
ls: /root: Permission non accordée
Oui, la réponse se situe dans la première ligne de sortie du programme. L'EUID du programme, celle fixée par le SUID bit est bien 0 (root) mais l'uid (celle utilisée pour l'appel du script) est 501. Or, 501 correspond d'après mon fichier /etc/passwd à l'utilisateur xavier. D'où un problème dans le comportement attendu ! En fait le script est appelé avec l'UID et non l'EUID. Il faut donc faire un pas supplémentaire et dire au programme de lancer le script en temps que root, c'est à dire faire en sorte que l'UID devienne l'EUID.

Ça marche mais c'est dangereux

Celà n'est rendu possible que parce que le programme est SUID root. Un programme lancé par le root peut prendre l'UID de n'importe qui, par contre un programme lancé par un utilisateur classique ne peut prendre comme UID que son EUID. C'est à dire l'UID de l'utilisateur qui l'a créé, sous réserve qu'il ait placé le SUID bit à 1.
Bref ! Ce changement se fait grâce à la fonction setreuid. Les fonctions seteuid, setuid et setreuid servent à manipuler les UID et EUID d'un programme. setuid modifie prend un paramètre qu'il affecte à l'UID et l'EUID. seteuid prend un paramètre qu'il affecte à l'EUID. setreuid en prend deux, affect le premier à l'UID et le second à l'EUID. Si un de ces paramètres vaut -1 la valeur correspondante n'est pas changée.
DONC setuid(ID) est équivalent à setreuid(ID, ID) et seteuid(ID) est équivalent à setreuid(-1, ID).

Voici l'illustration de cette partie dans le fichier lanceur_de_script_2.c :

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>

extern char **environ;
extern int errno;

int main (int argc, char **argv) {
  uid_t uid, euid;
  uid=getuid();
  euid=geteuid();
  printf ("UID %d - EUID %d\n", uid, euid);
  setreuid (euid, euid);
  uid=getuid();
  euid=geteuid();
  printf ("UID %d - EUID %d\n", uid, euid);
  execve("/usr/bin/voir_rep_root", NULL, environ);
  printf ("Error : %d\n", errno);
  return errno;
}
Une fois le code mis à jour :
[root@ZAZOU /root]# gcc -o lanceur_de_script_2 lanceur_de_script_2.c
[root@ZAZOU /root]# chmod 4111 lanceur_de_script_2
[root@ZAZOU /root]# mv lanceur_de_script_2 /usr/bin/
[root@ZAZOU /root]# su xavier
[xavier@ZAZOU /root]$ lanceur_de_script_2
UID 501 - EUID 0
UID 0 - EUID 0
Contenu du répertoire de root
.            .bash_history  .cshrc          .toprc  Mail
..           .bash_logout   .mysql_history  .vimrc  lanceur_de_script.c
.Xauthority  .bash_profile  .parsecrc       .wmrc   lanceur_de_script_2.c
.Xdefaults   .bashrc        .tcshrc         .zshrc

Vous voyez qu'après l'appel à setreuid, nous sommes non seulement UID root mais aussi EUID root et que de par le fait, tous les scripts que nous appelons s'exécutent avec l'identité root... Donc, ça marche ! Fin de l'article.
Mais non, ce n'est pas la fin de l'article car vous vous rendez sûrement compte à ce niveau que le script n'a pas besoin d'être SUID root pour que celà fonctionne...
Imaginons, un instant que l'on remplace l'appel statique au script par un appel à un script passé en paramètre... Ca devient super pratique mais aussi super dangereux, n'importe quel script pouvant être exécuté en temps que root...

Nicking ze danger !

Ouaip, ça marche ! Mais c'est dangeureux et cet article est là pour vous en faire prendre conscience... D'autre part, un autre point à comprendre est que setreuid vous permet certes de gagner l'accés au privilèges du root dans un programme SUID mais qu'il vous permet aussi de les abandonner lorsque vous n'en avez plus besoin.
Celà demande des exemples, allons y, créons le fichier createur_de_fichier.c en temps que root pour prendre conscience du côté éphémère de la toute puissance :

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>

extern char **environ;
extern int errno;

int main (int argc, char **argv) {
  FILE * fd;
  uid_t uid, euid;

  uid=getuid();
  euid=geteuid();
  printf ("UID %d - EUID %d\n", uid, euid);
  if (!(fd = fopen("/root/test", "w")))
    printf ("Je n'ai pas pu créer le fichier /root/test avant setreuid\n");
  else {
    printf ("J'ai pu créer le fichier /root/test avant setreuid\n");
    fclose(fd);
  }
  setreuid (euid, uid);
  uid=getuid();
  euid=geteuid();
  printf ("UID %d - EUID %d\n", uid, euid);
  if (!(fd = fopen("/root/test", "w")))
    printf ("Je n'ai pas pu créer le fichier /root/test après setreuid\n");
  else {
    printf ("J'ai pu créer le fichier /root/test après setreuid\n");
	fclose(fd);
  }
}
Dans le fichier ci-dessus on tente de créer un fichier en c dans un programme SUID puis après avoir inversé l'uid et l'euid. On se rend alors compte que contrairement au cas précédent, c'est l'uid qui compte lors des actions du programme.
[root@ZAZOU /root]# gcc -o createur_de_fichier createur_de_fichier.c
[root@ZAZOU /root]# chmod 4111 createur_de_fichier
[root@ZAZOU /root]# mv -f createur_de_fichier /usr/bin/
[root@ZAZOU /root]# su xavier
[xavier@ZAZOU /root]$ createur_de_fichier
UID 501 - EUID 0
J'ai pu créer le fichier /root/test avant setreuid
UID 0 - EUID 501
Je n'ai pas pu créer le fichier /root/test après setreuid
[xavier@ZAZOU /root]$

Pour ce qui est du danger, il suffit de créer un programme en c permmettant d'exécuter un script passé en paramètre mais qui vérifie si ce script fait partie d'une liste de scripts autorisés stockée chez le root, dans le répertoire /root/.authoscripts/, dans le fichier liste. D'autre part, les scripts sont cherchés uniquement dans ce répertoire, ainsi, il n'y a que le root qui puisse en ajouter. Il y a quelques instructions à taper :

[root@ZAZOU /root]# mkdir .authoscripts
[root@ZAZOU /root]# cd .authoscripts/
[root@ZAZOU .authoscripts]# touch liste
[root@ZAZOU .authoscripts]# cat >> liste
voir_rep_root
< Ctrl+D >
[root@ZAZOU .authoscripts]#
[root@ZAZOU .authoscripts]# mv /usr/bin/voir_rep_root ./
Créons maintenant la prise en charge des SUID Scripts. On ajoute aux contraintes qu'il doit être possible de passer des paramètres aux scripts.
Tout celà nous donne ce code ci :
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

extern char **environ;
extern int errno;

int main (int argc, char **argv) {
  FILE * fd;
  uid_t uid, euid;
  int isOK = 0;
  char tmpBuff[256];

  if (argc<2) {
    fprintf(stderr, "USAGE : suscript nom_du_script param_1 param_2 ...\n");
    return 1;
  }
  if (!(fd=fopen("/root/.authoscripts/liste", "r"))) {
    fprintf(stderr, "ERREUR : Impossible d'ouvrir le fichier liste dans\
 /root/.authoscripts.\n");
    return 2;
  }
  while (!feof(fd)) {
    fscanf(fd, "%s", tmpBuff);
    if (!strcmp(argv[1], tmpBuff)) {
      isOK++;
      break;
    }
  }
  fclose (fd);
  if (!isOK) {
    fprintf(stderr, "ERREUR : %s n'est pas un script autorisé...\n", argv[1]);
    return 3;
  }
  uid=getuid();
  euid=geteuid();
  setreuid (euid, euid);
  sprintf (tmpBuff, "/root/.authoscripts/%s", argv[1]);
  execve (tmpBuff, &argv[1], environ);
  printf ("Erreur : %d - %s\n", errno, strerror(errno));
  return errno;
}
Que l'on compile et range ainsi :
[root@ZAZOU /root]# gcc -o suscript suscript.c
[root@ZAZOU /root]# chmod 4111 suscript
[root@ZAZOU /root]# mv suscript /usr/bin
Dont on vérifie le bon foncionnement grâce à la série de commandes qui suit.
[root@ZAZOU /root]# cd .authoscripts/
[root@ZAZOU .authoscripts]# cat > script_test
#!/bin/sh
echo id: `whoami`
echo "Params :" $*
< Ctrl+D >
[root@ZAZOU .authoscripts]# chmod a+x script_test
[root@ZAZOU .authoscripts]# su xavier
[xavier@ZAZOU .authoscripts]$ cd
[xavier@ZAZOU xavier]$ suscript script_test 1 2 3 4 5 6 7 8 9 0
id: root
Params : 1 2 3 4 5 6 7 8 9 0

En savoir plus

man execve et entre autres, la section NOTES :

NOTES
       SUID and SGID processes can not be ptrace()d SUID or SGID.

       A maximum line length of 127 characters is allowed for the
       first line in a #! executable shell script.

       Linux ignores the SUID and SGID bits on scripts.

Vous avez compris le pourquoi de cet article ?

man getuid

man setuid

man setreuid

man getlogin

man cuserid

man chmod

Tshaw

C'est tout pour cette fois !
N'hésitez pas à m'envoyer vos commentaires par mail, il reste sûrement des améliorations à faire.
Retrouvez l'article remis en forme, les sources, archives et binaires sur mon site perso..., http://perso.club-internet.fr/xgarreau/
Vous pouvez également me contacter dans le forum Développement de Léa, je le regarde (très) souvent.
A bientôt,
Xavier GARREAU
Ingénieur de recherche PRIM'TIME TECHNOLOGY : http://www.prim-time.com/
Membre fondateur et président (2002-20??) du ROCHELUG : http://lug.larochelle.tuxfamily.org/
Pigiste ;-) (Léa-Linux et LinuxMag-France)

Haut


Présentation de DCOP

par Teotihuacan

Ceci est une courte présentation de DCOP.

Présentation

DCOP (Desktop COmmunication Protocol) est un service interne à KDE permettant de communiquer avec les applications KDE. Vous allez donc pouvoir communiquer avec ces applications grâce à votre shell. Cela inclut la récupération d'informations, ou la demande d'exécution d'actions à ces applications.

DCOP est intégré au librairies de KDE. Vous n'avez pas à vous soucier de l'installation et de la configuration de DCOP. Si vous avez installé Kdelib (une version au moins égale à 2.0) sur votre distribution, il se lancera automatiquement.

Les programmeurs d'une application KDE ne sont pas obligés d'utiliser DCOP. Mais cela constitue certainement un plus. Pour une application, un programmeur aura donné la possibilité à l'utilisateur de faire un certain nombre d'appels.

Quant à la nature des appels DCOP que l'utilisateur final pourra faire à partir de son shell, il s'agit ni plus ni moins que de fonctions, avec tout ce que cela implique : une fonction a un nom, des arguments et un résultat. L'utilisateur fera appel à la fonction grâce au nom de la fonction, et en fonctions des arguments qu'il aura entré, la fonction lui retournera un résultat. Ce résultat peut être une information affichée et/ou une action que va effectuer le programme.

Utilisation

Nous allons ici aborder la façon dont l'utilisateur va pouvoir communiquer avec le service DCOP à partir de son shell. L'utilisation de DCOP est très simple, elle se fait par la commande dcop.

La syntaxe pour faire un appel à une fonction est la suivante :

$ dcop APPLICATION INTERFACE FONCTION ARGUMENTS

Avec APPLICATION : C'est le nom de l'application que l'on vise.
INTERFACE : Les fonctions DCOP d'une application sont regroupées par interface.
FONCTION : C'est le nom de la fonction que l'on souhaite appeler.
ARGUMENTS : Ce sont les arguments que l'on souhaite passer à la fonction. Il peut y en avoir un, plusieurs ou aucun. S'il y a plusieurs arguments, il suffit de les séparer par un espace.

Mais heureusement, vous n'êtes pas obliger de connaitre toutes ces informations par coeur.

Certaines application peuvent avoir plusieurs instances, par exemple Konqueror et Kwrite, Pour désigner l'application avec dcop, il faut faire suivre le nom de l'application d'un tiret et de son PID. Ainsi, dans la liste des applications accessibles par DCOP, on pourra retouver un "konqueror-7327" et un "konqueror-5837" par exemple.

Exemple

Maintenant que je vous ai présenté le fonctionnement basique de DCOP, il est temps de passer à la pratique avec un exemple.

Ouvrez une console sur KDE.

Entrer la commande dcop pour afficher la liste des applications accessibles. Vous obtiendrez alors une liste telle que celle-ci :

$ dcop
KWeatherService
konsole-7205
kwin
kicker
amarok
kded
knotify
kio_uiserver
kcookiejar
konqueror-7327
rssservice
korgac
konsole-7165
klauncher
khotkeys
kopete
kdesktop
korn
klipper
ksmserver
konqueror-5837

Pour utiliser une application que tout le monde a, essayons avec KWin (le gestionnaire de fenêtres de KDE). Affichons maintenant les interfaces DCOP ouvertes pour KWin :

$ dcop kwin
qt
KWinInterface (default)
MainApplication-Interface

Voyons alors les fonctions de "KWinInterface" :

$ dcop kwin KWinInterface
QCStringList interfaces()
QCStringList functions()
ASYNC cascadeDesktop()
ASYNC unclutterDesktop()
ASYNC reconfigure()
ASYNC killWindow()
void refresh()
void doNotManage(QString)
void showWindowMenuAt(unsigned long int winId,int x,int y)
void setDesktopLayout(int orientation,int x,int y)
bool setCurrentDesktop(int)
int currentDesktop()
void nextDesktop()
void previousDesktop()
void circulateDesktopApplications()

Intéressons-nous dans un premier temps à la fonction int currentDesktop(). Pour ceux qui ne sont pas habitués à ce type de syntaxe (que l'on retrouve en C par exemple), le int représente le résultat de la fonction. Là, c'est un entier. Entre les parenthèse, il n'y a rien, ça veut donc dire qu'il n'y a pas d'argument à transmettre à la fonction. Il ne reste plus qu'à tester :

$ dcop kwin KWinInterface currentDesktop
4

La fonction m'a donc retourné un 4. Il n'y a pas de description pour cette fonction, mais son nom est assez explicite : on imagine bien qu'elle m'a renvoyé le bureau sur lequel j'étais, en l'occurence le bureau 4.

Essayons maintenant la fonction void nextDesktop() de cette même interface. Le void au début m'informe que cette fonction ne renvoie rien. Je m'attends donc à ce que l'appel de cette fonction effectue une action plutôt que de renvoyer une information. Ainsi, en entrant la commande

$ dcop kwin KWinInterface nextDesktop

Je me retrouve sur le bureau suivant, dans mon cas soit le 5, soit le 1 (les bureaux tournent en boucle, si le 4 est mon dernier, je reviens au 1). Encore une fois, le nom de la fonction est assez explicite.

Essayons maintenant une fonction avec un argument : bool setCurrentDesktop(int). Comme vous l'imaginez, cette fonction va permettre d'afficher le bureau que l'on souhaite. Le bool au début m'informe que la fonction va me renvoyer un booléen, soit VRAI (true) ou FAUX (false). Vraissemblablement, le "VRAI" est obtenu si le changement a pu se faire. Entre les parenthèse, il y a cette fois un int, c'est l'argument que l'on foit donner à la fonction. Ici, c'est donc un entier. Vous imaginez bien que cet entier va représenter le bureau que l'on souhaite afficher. Ainsi, pour afficher le bureau 2 :

$ dcop kwin KWinInterface setCurrentDesktop 2

KDCOP

KDCOP est une application KDE qui répertorie toutes les entrées DCOP actives. Vous pouvez vous y référer pour visualiser facilement les appels que vous pouvez faire. Cette application est accessible en lançant la commande kdcop.

En plus de visualiser la liste des entrées possibles, vous pouvez y faire directement vos appels.

KDCOP
KDCOP en action

Application

Vous allez pouvoir utiliser les possibilités de DCOP à plusieurs niveaux :

Sources

IBM Developperwork : DCOP

Haut


Les Librairies C (linux)

par Xavier GARREAU (alaide)

Dans cette page, je vais évoquer la rédaction, la compilation et l'utilisation des librairies en C. Je traiterai avant tout de développement sous linux bien que la majorité des explications soient applicables à d'autres systèmes d'exploitation, y compris ceux qu'on voudrait voir passer par la fenêtre ;-).


Introduction

Intérêt ?

Imaginez : Vous avez développé une série de fonctions très utiles pour un projet, prenons l'exemple d'un tri par bulles. Un jour, vous devez effectuer une nouvelle série de tris. Vous ouvrez le projet et passez la journée à faire du copier coller ?!?! NON !!! car vous aviez pensé à créer un librairie contenant la fonction principale du tri, n'est ce pas ?
Les librairies permettent de se constituer des bibliothèques de fonctions réutilisables et distribuables.

Vous voulez un autre avantage des librairies ?
On les compile à part ! Si vous avez comme moi un pc qui se fait vieux, vous savez que la compilation peut prendre pas mal de temps. La bonne nouvelle c'est qu'une fois les librairies compilées, vous ne les recompilez pas à chaque fois que vous recompilez votre projet. Tout au plus, vous les liez, s'il s'agit de bibliothèques statiques.

Qu'est ce qu'il y a dedans ?

Une librairie est composée d'un fichier d'en têtes (headers en anglais, d'ou le .h), ce sont les fichiers .h qui doivent vous dire quelque chose, non ? Et d'un partie binaire.
Le fichier d'en têtes, permet de "déclarer" les fonctions présentes dans la librairie, le type de valeurs qu'elles renvoient et les paramètres à leur passer.
La partie binaire est constituée d'un ou plusieurs fichiers c compilés. Ces fichiers c contiennent l'implémentation des prototypes de fonctions déclarées dans le fichier d'en têtes.
Vous pouvez modifier le code de la librarie autant que vous le désirez, du moment que vous respectez les prototypes définis dans le fichier .h.

Statique, Dynamique ?

Ouais, entrons dans le vif du sujet. Qu'est ce qu'une librairie statique ? et une librairie dynamique ? Quelles sont les avantages et inconvénients de chacune ?

Plus d'infos sur cette partie ?

Consultez les pages man de dlopen et dlsym et celles qui y sont conseillées et tapant dans la console
	man dlopen
	man dlsym

Rédaction : remarques

Quelques généralités

Si vous voulez que votre librairie soit réutilisable et efficace, vous devez respecter quelques notions fondamentales de la programmation.

Les "chemins"

La directive #include : pour inclure un fichier dans un autre en c, on utilise la directive #include.
On spécifie le chemin relatif entre le fichier incluant et le fichier inclus, par exemple :
#include "mesH/monTest.h"
ou bien on place le fichier .h dans le répertoire ou le compilateur s'attend à trouver les fichiers .h. (regardez dans /usr/include par exemple) et on donne le chemin relatif par rapport à ce répertoire, par exemple :
#include <orb/orbit.h>
Nota Bene : On peut également spécifier d'autres chemins de recherche grâce à l'option -I de gcc, par exemple, pour y inclure le répertoire courant :
gcc -I. etc...

Pour devenir incollables sur ces amusantes petites choses, apprenez par coeur les man pages de gcc et ld. ;-)

Plus d'infos sur cette partie ?

Consultez les pages man des fonctions d'allocation dynamique de mémoire, du compilateur, du linker et celles qui y sont conseillées et tapant dans la console :
	man malloc
	man realloc
	man calloc
	man free
	man gcc
	man ld

On rentre enfin dans le vif du sujet avec cette partie. On va en effet maintenant suivre un exemple pas à pas pour construire une librarie, la compiler, l'utiliser dans un exécutable sous ses formes statiques et dynamiques.
On va prendre l'exemple d'un librairie comportant une fonction permettant de trier un tableau contenant des entiers longs. La méthode de tri sera le tri par bulles. Je ne suis pas vraiment sûr que ce soit cet algorithme là mais ça marche alors ... Roule !



Rédaction : le fichier d'en têtes

Bien entrons dans le dedans du vif du sujet ! ouvrez un éditeur de texte et tapez ça dans un fichier tri_a_bulles.h :

tri_a_bulles.h
/*
 * tri_a_bulles.h
 *
 * Quelques fonctions pour opérations basiques sur un tableau de longs.
 *
 * auteur: Xavier GARREAU : xgarreau@club-internet.fr
 *
 * web : http://perso.club-internet.fr/xgarreau/
 *
 * dmodif: 13.03.2000
 *
 */
#ifndef _TRI_A_BULLES_H
#define _TRI_A_BULLES_H 1

/*
 * test_case_tableau : Dans le tableau pointé par addr_tableau,
 *   Vérifie que la case case case_tableau est bien placée par rapport à celle qui la précède
 *   Si ce n'est pas le cas, permute les cases et se rappelle sur la case précédente.
 *   La récurrence s'arrête quand
 *     la case case case_tableau est bien placée par rapport à celle qui la précède
 *     ou bien si case_tableau vaut prem_case_tableau.
 *   Ne renvoie rien.
 *
 */
void test_case_tableau ( long * addr_tableau,
                         int taille_tableau,
                         int case_tableau,
                         int prem_case_tableau );

/*
 * permut_cases :
 *   permute les valeurs des cases case_1 et case_2 du tableau pointé par addr_tableau
 *   Ne renvoie rien.
 *
 */
void permut_cases ( long * addr_tableau,
                    int case_1,
                    int case_2 );

/*
 * test_tableau :
 *   renvoie le tableau pointé par addr_tableau trié (ordre croissant)
 *     de la case prem_case_tableau
 *     à la case taille_tableau-1
 *
 */
long * test_tableau ( long * addr_tableau,
                      int prem_case_tableau,
                      int taille_tableau );

/*
 * nb_cases_tableau :
 *   Renvoie le nombre de cases du tableau pointé par addr_tableau
 *     càd nombre de cases allouées
 *     ou première case contenant (long)NULL si le tableau en contient une.
 *
 */
int nb_cases_tableau ( long * addr_tableau );

#endif

Si vous êtes familier avec les fichiers .h, pas de problème. Sinon, disons qu'on se contente de définir les "prototypes" des fonctions, on écrira leur corps dans un fichier .c.
Les fichiers .h permettent au compilateur de connaitre les prototypes des fonctions qu'il rencontre dans les différents fichiers.c qui les utilisent.

Je m'explique ! Les fonctions définies dans un fichier .c peuvent être utilisées dans un autre, ça vous le savez ! (hein ? vous le savez ?) Il suffit de préciser au compilateur tous les fichiers à compiler (gcc fic1.c fic2.c ... ficn.c). Si par hasard vous commettez une erreur en appelant une fonction, vous verrez que la compilation se passera sans problème, MAIS, lors de l'exécution vous obtiendrez des résultats inattendus ou pire, une erreur. Brrrrrrrrrrrr ... Flippant non ?

Bon, si on programmait ? Un petit peu de récursivité maintenant ? GO ! BANZAI !

Rédaction : le fichier c

Ouvrez un éditeur de texte et tapez ça dans un fichier tri_a_bulles.c :

tri_a_bulles.c
/*
 * tri_a_bulles.c
 *
 * Quelques fonctions pour opérations basiques sur un tableau de longs.
 *
 * auteur: Xavier GARREAU : xgarreau@club-internet.fr
 *
 * web : http://perso.club-internet.fr/xgarreau/
 *
 * dmodif: 13.03.2000
 *
 */
#include "tri_a_bulles.h"

void test_case_tableau ( long * addr_tableau,
                         int taille_tableau,
                         int case_tableau,
                         int prem_case_tableau ) {
  if ( case_tableau > prem_case_tableau )
    if (addr_tableau[case_tableau] < addr_tableau[case_tableau-1]) {
      permut_cases ( addr_tableau,
                     case_tableau,
                     case_tableau-1 );
      test_case_tableau ( addr_tableau,
                          taille_tableau,
                          case_tableau-1,
                          prem_case_tableau );
    }
}

void permut_cases ( long * addr_tableau,
                    int case_1,
                    int case_2 ) {
  long tempo;
	
  tempo = addr_tableau[case_1];
  addr_tableau[case_1] = addr_tableau[case_2];
  addr_tableau[case_2] = tempo;
}

long * test_tableau ( long * addr_tableau,
                      int prem_case_tableau,
                      int taille_tableau ) {
  int i;
	
  for (i=prem_case_tableau ; i<taille_tableau ; i++)
    test_case_tableau (addr_tableau,
                       taille_tableau,
                       i,
                       prem_case_tableau);
 	
  return addr_tableau;
}

int nb_cases_tableau ( long * addr_tableau ) {
  int nb_cases;
	
  nb_cases=0;
  while (addr_tableau[nb_cases]) nb_cases++;
  return (nb_cases);
}

Finalement ça s'est bien passé non ? Pas si compliqué !
Bon si on veut utiliser ça il va falloir créer une application qui en a besoin ! On y va ?

Rédaction : le fichier de l'application

Maintenant que nous avons les sources de notre librairie, prêtes à être compilées et liées, il va falloir penser à construire une application pour utiliser les fonctions que l'on y a mis ! Ce sera bientôt chose faite si vous voulez bien vous prêter encore un peu au jeu de cette dernière fastidieuse saisie.

Ouvrez un éditeur de texte et tapez ça dans un fichier test.c :

test.c
/*
 * test.c
 *
 * Une application qui utilise les fonctions de la librairie tri_a_bulles
 *
 * auteur: Xavier GARREAU : xgarreau@club-internet.fr
 *
 * web : http://perso.club-internet.fr/xgarreau/
 *
 * dmodif: 14.03.2000
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include "tri_a_bulles.h"

int main (int argc, char * argv[]) {
  long * tab;
  int i;

  /* créée un tableau de 10 longs */
  tab = (long *)malloc( 10 * sizeof (long) );

  /* Initialise le générateur de nombre aléatoires avec ...
   * L'heure de lancement ...
   * voir man random ou man rand pour le pourquoi de la chose !!!
   * voir man time pour le comment !!!
   */
  srandom((int)time((time_t *)NULL));

  /* remplit le tableau avec une suite de nombres pseudo-aléatoires
   * et affiche le contenu.
   */
  for (i=0; i<10; i++) {
    tab[i] = random();
    printf ("tableau[%d] = %ld\n", i, tab[i]);
  }

  /* Affiche le nombre de cases su tableau retourné par la librairie */
  printf ("\nTaille du tableau : %d\n", nb_cases_tableau (tab));

  /* Permute 2 cases et affiche le tableau résultant */
  permut_cases (tab, 2, 8);
  printf ("\nTableau après permutation des cases 2 et 8.\n");
  for (i=0; i<10; i++)
    printf ("tableau[%d] = %ld\n", i, tab[i]);

  /* Trie le tableau et affiche le résultat */
  printf ("\nTableau trié par la fonction de la librairie\n");
  test_tableau ( tab, 0, nb_cases_tableau (tab) );
  for (i=0; i<10; i++)
    printf ("tableau[%d] = %ld\n", i, tab[i]);

  return 0;
}

Bien ! Maintenant qu'on a tous les bouts, on va pouvoir compiler, lier, exécuter, etc...

Compilation de tous les binious

Bon, et bien, nous y voilà, on a tout ! Il ne nous reste plus qu'à tout mettre nesemble selon différentes méthodes. On va commencer par la méthode du projet unique, sans librairies.
Après ça, on va se faire une petite librairie statique, ensuite, on va mettre en place une librarie dynamique (ou shared object, .so chez les pingouins, .dll chez les défenestrés !)
On n'abordera toutefois pas le cas de la construction des dll car franchement, ce serait perdre du temps pour rien. Je suis pour laisser les gens qui n'ont que ça pour occupper leurs tristes journées générer et utiliser (de façon HYPER galère) les librairies dynamiques en envirronnement ouinouin. Ici, je pense que nous sommes entre gens sérieux, on développe donc sous linux, FreeBSD, solaris ou autres unix. Franchement, je m'excuse de sembler aussi méchant vis à vis de wintruc mais quand vous aurez comparé les qualités des deux systèmes (au moins en matière de support de développement), je suis presque sûr que vous penserez comme moi.

A la fin de cette série d'infos, je vous renverrai sur les bons coins pour utiliser les makefiles, ainsi que les merveilleux outils GNU que sont autoconf, autoscan, automake et leurs copains.

Sans librairies

Placez vous dans le répertoire du projet puis tapez ça dans la console :
gcc -o test test.c tri_a_bulles.c
Ceci génère un exécutable test que l'on lance, toujours en étant placé dans le répertoire de projet, en tapant :
./test

Avec librairies statiques

Placez vous dans le répertoire du projet puis tapez ça dans la console :
gcc -c test.c
Vous obtenez un objet binaire test.o. C'est une compilation sans édition de liens. Compilez de même la librairie en tapant :
gcc -c tri_a_bulles.c
Ce qui donne tri_a_bulles.o. Comme je l'ai déjà dit, si plusieurs fichiers composent la bibliothèque, on aurait tapé
gcc -c fic1.c fic2.c ...
Il faut ensuite créer la bibliothèque. Pour celà, tapez :
ar -q tri_a_bulles.a tri_a_bulles.o
S'il y avait eu plusieurs fichiers ... référez vous au pages man de ar. Regardez surtout les options a, q et c !

Ok, maintenant on lie le tout :

gcc -o test test.c tri_a_bulles.a
Petite précision, normalement, l'éditeur de liens GNU de linux c'est ld. Ceci dit, ça marche avec gcc parce qu'il "l'appelle" alors, on ne vient pas se plaindre. Toutefois, dans le doute et pour en savoir plus, tapez man ld

On ne se laisse pas décourager pour autant et on exécute :

./test
C'est bizzare non ? Ca marche pareil. Bienvenue dans le monde du codage efficace ...

Avec librairies dynamiques

Placez vous dans le répertoire du projet puis tapez ça dans la console :
gcc -c test.c
Puis :
gcc -c tri_a_bulles.c
Jusque là, vous n'êtes pas perdus, c'est pareil ! Oui, mais, maintenant on génère la librairie dynamique :
gcc -o tri_a_bulles.so -shared tri_a_bulles.o
Puis on génère l'exécutable en lui disant qu'il fera appel à la librarie dynamique tri_a_bulles.so :
gcc -o test test.o tri_a_bulles.so
Puis content qu'on est, on exécute :
./test
./test: error in loading shared libraries:
tri_a_bulles.so:
cannot open shared object file:
No such file or directory
Et oui, le bon des librairies partagées c'est qu'elles sont liées au moment de l'exécution. Or, les librairies partagées, le système va les chercher dans un répertoire contenu dans la variable d'environnement LD_LIBRARY_PATH. Et bien, par défaut le répertoire courant n'en fait pas partie ... C'est con ?
Non, pas tant que ça ! Et il y a des outils, là encore pour faciliter les choses ...
Tapez :
ldd ./test
tri_a_bulles.so => not found
libc.so.6 => /lib/libc.so.6 (0x4001a000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Visiblement, il y a une couille !
Alors, soit vous copiez votre librairie dans les répertoires qui contiennent des librairies dynamiques (/usr/lib, /usr/i486-linux-libc5/lib ou /usr/X11R6/lib chez moi), vous trouverez ça dans le fichier /etc/ld.so.conf.
Ou alors ajouter votre répertoire à la variable en tapant
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
Dans les deux cas, vous obtiendrez le résultat attendu en tapant :
./test

En conclusion


Avec les libraries statiques :


Avec les libraries dynamiques :

Notons que :

Le résultat

J'allais oublier !!!
Normalement, le programme affiche un truc comme ça :
xavier@Rooty
Rep: tabLong
$ ./test
tableau[0] = 1613489603
tableau[1] = 866884903
tableau[2] = 295298324
tableau[3] = 1614953842
tableau[4] = 1111079167
tableau[5] = 950260573
tableau[6] = 901366332
tableau[7] = 745370511
tableau[8] = 2063084132
tableau[9] = 374329280

Taille du tableau : 10

Tableau après permutation des cases 2 et 8.
tableau[0] = 1613489603
tableau[1] = 866884903
tableau[2] = 2063084132
tableau[3] = 1614953842
tableau[4] = 1111079167
tableau[5] = 950260573
tableau[6] = 901366332
tableau[7] = 745370511
tableau[8] = 295298324
tableau[9] = 374329280

Tableau trié par la fonction de la librairie
tableau[0] = 295298324
tableau[1] = 374329280
tableau[2] = 745370511
tableau[3] = 866884903
tableau[4] = 901366332
tableau[5] = 950260573
tableau[6] = 1111079167
tableau[7] = 1613489603
tableau[8] = 1614953842
tableau[9] = 2063084132
En scoop, vous apprenez que mon pc s'appelle Rooty (ce qui vient des Régulateurs de Bachman), que je me connecte sous le nom de xavier et que ce projet se trouve dans le répertoire tabLong. En outre le symbole $ précise que je ne suis qu'un simple utilisateur, le root ayant droit à un superbe #. Voilà !

Pour ce qui est du renvoi sur la doc sur autoscan, autoconf, automake et leurs copains ce que j'ai trouvé de plus sympa, ce sont les infos pages du gnome-help-browser. Si vous ne l'avez pas installé, vous pouvez les consulter dans la console en tapant info automake ou info autoscan, etc ... !

Pour trouver moults docs de developpement, voyez http://developer.gnome.org/. Il existe la même chose avec les libs kde sur http://developer.kde.org/. Il existe bien d'autres sources d'infos mais ce sont celles que je préfère.

Un petit conseil, si vous aimez le c, installez les librairies gtk+/gdk/glib/imlib/ORBit C'est le top.
Vous cherchez un environnement de développement ? Choisissez gIDE et glade si vous avez des affinités avec le projet et les librairies du projet gnome ou kdevelop pour affinités avec KDE.
Comme débogueur je dois dire que kdbg est génial, d'autant qu'il s'intègre via DCOP dans kdevelop. Mais xemacs est pas mal non plus et gdb tout seul aussi, le tout c'est de connaitre !
En bref, prenons ce qui existe de meilleur partout. Bienvenue dans linux et à bientôt pour de nouvelles aventures ...

Haut


Utiliser la librairie gd

par Xavier GARREAU (alaide)

Vous avez sûrement déjà vu ou utilisé la librairie gd. En effet, si vous créez les statistiques de votre serveur web avec webalizer ou si vous avez déjà généré des images avec php, vous avez déjà bénéficié des bienfaits de cette librairie. Il s'agit d'une librairie permettant de générer des images dans un programme. Initialement gd est écrite pour le C mais il existe des portages pour perl, tcl, pascal, haskell. Bien évidemment, gd est utilisable depuis php.


Introduction

Présentation

Traduction de la présentation de gd1.8.3 sur le site officiel.

" gd1.8.3 permet la création d'images aux formats png, jpeg et wbmp, mais pas gif. C'est une bonne chose, png est un format plus compact, et la "full compression" est permise. le format JPEG est bien pour les images photographiques et est plus compatible avec les browsers actuels que le format png. le format WBMP sert aux appareils sans fils (ndt: applications wap) mais pas aux browsers classiques. Le code existant basé sur d'anciennes versions de gd qui prenaient en charge le format gif devra être modifié. Il faudra utiliser gdImagePng ou gdImageJpeg en lieu et place des appels à gdImageGif. Ne nous demandez pas de vous fournir d'anciennes versions de gd. Unisys possède une licence sur l'algorithme de compression LZW qui est utilisé pour générer les "images GIF compressées". La meilleure solution est d'adopter des formats modernes, libres, bien compressés tels que PNG et JPEG autant que possible et le plus tôt sera le mieux. "
Nota : Cet article est basé sur la version 1.8.3 de la librairie gd.

Il vous faut

Pour installer la version minimale de gd1.8.3 vous devez avoir sur votre système (les librairies + les .h):

Si vous désirez inclure le support du format jpeg, vous devez également avoir la librairie jpeg6b ou supérieure.
Si vous désirez inclure le support des TrueTypeFonts, vous devez également avoir la librairie Freetype et quelques polices ttf.
Si vous désirez inclure le support de chargement d'images XPM, vous devez avoir X et la librairie XPM sur votre système.

Préparatifs

Trouver le tout

Sur ma machine, j'avais déjà toutes les librairies et fichiers .h requis (sauf gd naturellement et freetype) mais ça ne sera peut être pas le cas pour tout le monde. J'ai donc regroupé ci-dessous les emplacements des choses dont vous pourriez avoir besoin, y compris gd bien sûr.

Compilation et installation

Vous avez à ce stade compilé et installé les pré-requis. Nous sommes donc prêts à aller plus avant.
Commencez par vous placer dans le répertoire où vous avez placé l'archive de gd puis tapez tar xvfz gd-1.8.3.tar.gz. Cela vous créé un répertoire gd-1.8.3. Débarrassez vous de l'archive éventuellement en tapant rm -f gd-1.8.3.tar.gz.
Placez vous dans le répertoire en tapant cd gd-1.8.3. Editez le makefile en fonction des librairies dont vous disposez et de leurs emplacements.

1. Mettez un '#' devant les lignes commençant par LIBS puis tapez la vôtre comme suit :
LIBS=-lm -lgd -lpng -lz [-ljpeg] [-lttf] [-lXpm -lX11]
Expliquons ce que les options sont :

En clair, la ligne libs minimale pour réaliser des images png est LIBS=-lm -lgd -lpng -lz. C'est ce qui est saisi par défaut donc si cela vous va, ne changez rien à cette ligne.

2. Editez les lignes INCLUDEDIRS et LIBDIRS si vos librairies ne se trouvent pas dans les répertoires saisis par défaut.

3. Editez les lignes INSTALL_LIB, INSTALL_INCLUDE et INSTALL_BIN si les valeurs par défaut ne vous conviennent pas (respectivement /usr/local/lib, /usr/local/include et /usr/local/bin). Il est à noter que, généralement, ces répertoires ne sont pas parcourus par défaut. Donc, si vous êtes root et que vous voulez rendre accessible gd à tous sans vous poser de questions, remplacez ces lignes par :
INSTALL_LIB=/usr/lib
INSTALL_INCLUDE=/usr/include
INSTALL_BIN=/usr/bin

4. Mettez un '#' devant les lignes commençant par CFLAGS puis tapez la vôtre comme suit:
CFLAGS=-O [-DHAVE_XPM] [-DHAVE_JPEG] [-DHAVE_LIBTTF]
Expliquons :

Comme il est dit dans le Makefile, les lignes du dessous ne vous regardent pas. Vous pouvez quand même les modifier si vous voulez, notamment pour corriger le numéro de version ;-).

A ce point vous pouvez taper make. Tout devrait bien se passer. Continuer en tapant make install si vous êtes root ou si vous avez les droits en écriture dans les répertoires spécifiés dans l'étape 3. ci-dessus. Sinon, ce n'est pas grave, il vous suffira d'indiquer au compilateur où trouver les librairies et fichiers d'en-tête quand vous compilerez vos applications.

Utilisation

Je considère ici que vous avez installé les librairies dans des répertoires habituels, où le compilateur et le linker peuvent trouver les fichiers nécessaires.
Dans le cas contraire, créez un répertoire de travail où vous saisirez les exemples, copiez y le répertoire gd-1.8.3 et lors de la compilation, ajoutez après gcc -o expl_gd_x les options -I./gd-1.8.3 -L./gd-1.8.3.

Préambule

Avec gd vous pouvez créer vos images entièrement, à partir de rien, où vous servir d'images existantes. Les exemples simplistes ci-dessous vous montrent comment faire. Si vous avez déjà utilisé php pour réaliser des images, vous ne serez pas dépaysés par la syntaxe ;-).

Exemple1

Dans cet exemple nous allons créer une image simple, il s'agira d'une croix rouge sur fond blanc. Notez qu'il faut, dans l'ordre :

/*
 * Fichier expl_1.c
 */
#include <stdlib.h> /* Y'en aura bien besoin (pour le exit) */

#include <gd.h> /* On va utiliser gd */
/* Théoriquement il faut inclure stdio.h
   MAIS c'est fait dans gd.h donc ... */

int main(void) {
  gdImagePtr image; /* Pointeur vers notre image */
  FILE *image_png; /* Fichier image PNG */
  int rouge, blanc; /* Deux couleurs */

  /* On créée un image de 100 par 100 */
  image = gdImageCreate(100, 100);

  /*
   * On "alloue" deux couleurs.
   * Notez que la première sera la couleur de fond
   *
   * En paramètres : - l'image
   *                 - les valeurs de rouge, vert et bleu (de 0 à 255)
   */
  blanc = gdImageColorAllocate(image, 255, 255, 255);
  rouge = gdImageColorAllocate(image, 255, 0, 0);

  /*
   * On trace la croix, deux rectangles pleins qui se croisent
   * le premier de x=20,y=45 à x=80,y=55
   * le second de x=45,y=20 à x=55,y=80
   * tous les deux de couleurs rouge.
   */
  gdImageFilledRectangle(image, 20, 45, 80, 55, rouge);
  gdImageFilledRectangle(image, 45, 20, 55, 80, rouge);

  /* Ouvrir le fichier image en écriture. */
  image_png = fopen("expl1.png", "w");

  /* Créer l'image au format PNG. */
  gdImagePng(image, image_png);

  /* On ferme le fichier de l'image. */
  fclose(image_png);

  /* On détruit l'image. */
  gdImageDestroy(image);

  exit (0);
}

On compile en tapant
gcc -o expl_gd_1 expl_1.c -lgd -lpng
ou
gcc -o expl_gd_1 expl_1.c -I./gd-1.8.3 -L./gd-1.8.3 -lgd -lpng (Voir plus haut)
Après exécution par ./expl_gd_1, vous obtenez dans le répertoire courant une image expl1.png. résultat du code...

Nous avons vu ici la fonction permettant de créer des rectangles pleins. Mais, nous pouvons également avec gd créer des rectangles vides, des lignes continues où non (tirets, style défini par l'utilisateur), des polygones pleins ou non, des arcs et ce en utilisant des lignes basiques ou en créant des "pinceaux" (brushes) ou bien encore ajouter du texte ...

Exemple2

Nous allons maintenant écrire, en noir, en utilisant les polices par défaut de gd, contenues dans les fichiers fournis dans l'archive. Ce sont les fichiers gdfont*.h. A ces fichiers correspondent les polices gdFontTiny, gdFontSmall, gdFontMediumBold, gdFontLarge et gdFontGiant
Dans cet exemple, on va générer une image de la taille requise par les textes, ni plus ni moins. La hauteur et la largeur d'un caractère étant données par gdFontxxx->h et gdFontxxx->w.
Si on souhaite écrire verticalement, on utilisera la fontion gdImageStringUp.
Si on souhaite utiliser les polices TrueType on pourra se référer à la fonction gdImageStringTTF. Attention, pour les polices TT, les prototypes de fonctions sont différents et la récupération de l'espace occupé ne se fait pas de la même manière. D'autre part, les polices TT ne sont pas fournies dans l'archive gd, faites un find / -name *.ttf pour rechercher les polices sur votre système.

/*
 * Fichier expl_2.c
 */
#include <stdlib.h> /* Y'en aura bien besoin (pour le exit) */

#include <gd.h> /* On va utiliser gd */
/* Théoriquement il faut inclure stdio.h 
   MAIS c'est fait dans gd.h donc ... */

#include <gdfontt.h> /*on va utiliser la police gdFontTiny */
#include <gdfonts.h> /*on va utiliser la police gdFontSmall */
#include <gdfontmb.h> /*on va utiliser la police gdFontMediumBold */
#include <gdfontl.h> /*on va utiliser la police gdFontLarge */
#include <gdfontg.h> /*on va utiliser la police gdFontGiant */

int main(void) {
  gdImagePtr image; /* Pointeur vers notre image */
  gdFontPtr mesPolices[5]; /* tableau des polices */
  FILE *image_png; /* Fichier image PNG */
  int blanc, bleu; /* Nos deux couleurs */
  char *message = "Hello World !"; /* Un message original */
  int long_message; /* La taille du message */
  int tmp_larg, larg = 0, haut = 40; /* Dimension de l'image */
  int posx = 10, posy = 10; /* Position du texte */
  int i; /* Ca ressemble à une variable pour une boucle for ;-) */

  /* on remplit le tableau */
  mesPolices[0] = gdFontTiny;
  mesPolices[1] = gdFontSmall;
  mesPolices[2] = gdFontMediumBold;
  mesPolices[3] = gdFontLarge;
  mesPolices[4] = gdFontGiant;

  /* On calcule la longueur du message */
  long_message = strlen(message);

  /* On calcule les dimensions de l'image */
  for (i=0 ; i<5 ; i++) {
  	haut += mesPolices[i]->h;
  	larg = ( larg < ( 
  	 tmp_larg = long_message*mesPolices[i]->w + 20 ) 
  	                ) ? tmp_larg : larg;
  }

  /* On créée un image de larg par haut */
  image = gdImageCreate(larg, haut);

  /* On alloue deux couleurs. */
  blanc = gdImageColorAllocate(image, 255, 255, 255);
  bleu = gdImageColorAllocate(image, 0, 0, 88);

  /*
   * On écrit le texte avec les cinq polices en bleu.
   * On remet à jour la hauteur
   */
  for (i=0 ; i<5 ; i++) {
  	posy += mesPolices[i]->h;
  	gdImageString(image, mesPolices[i], posx, posy, message, bleu);
  }

  /* Ouvrir le fichier image en écriture. */
  image_png = fopen("expl2.png", "w");

  /* Créer l'image au format PNG. */
  gdImagePng(image, image_png);

  /* On ferme le fichier de l'image. */
  fclose(image_png);

  /* On détruit l'image. */
  gdImageDestroy(image);

  exit (0);
}

On compile en tapant
gcc -o expl_gd_2 expl_2.c -lgd -lpng
Après exécution par ./expl_gd_2, vous obtenez dans le répertoire courant une image expl2.png. résultat du code...

Exemple3

Nous savons à présent créer des images mais il peut être utile d'en utiliser une existante qui servira de base à notre création afin de créer un histogramme sur un fond dégradé, pour rendre une couleur d'image transparente où créer des boutons personnalisés sur une page web.

Prenons ce cas de figure comme exemple :
Vous avez une page qui permet de contacter les 10 personnes les plus actives d'un forum en cliquant sur un bouton contenant leur pseudo. Vous ne pouvez pas à l'avance savoir qui sera dans les 10 personnes ou non, vous avez donc un script qui vous donne les dix personnes et un lien vers eux. Examinons ce qu'il faut faire pour générer un bouton contenant leur nom.

Jusqu'à maintenant on a créé les images avec gdImageCreate. Il existe aussi :

Ces fonctions permettent de récupérer, pour traitement, une image existante. Nous allons travailler à partir d'un fichier image PNG contenant le bouton, vide, bout_in.png. résultat du code...
Je sais, ce n'est pas beau mais le but est de montrer ce que l'on peut faire, pas de faire beau. On pourra améliorer cela en partant d'un plus beau bouton et en utilisant une police TrueType pour le texte.
On va créer un cgi, pour changer. Il enverra l'image sur le flux standard, précédée de l'entête adéquate, ici Content-type: image/png suivi de deux retours à la ligne, comme le veux la norme.
/*
 * Fichier expl_3.c
 */
#include <stdlib.h> /* Y'en aura bien besoin (pour le exit) */

#include <stdlib.h> /* Pour le getenv */

#include <gd.h> /* On va utiliser gd */
/* Théoriquement il faut inclure stdio.h 
   MAIS c'est fait dans gd.h donc ... */

#include <gdfontmb.h> /* On va utiliser la police gdFontMediumBold */

int main(void) {
  gdImagePtr image; /* Pointeur vers notre image */
  FILE *image_png_in; /* Fichier image PNG */
  int jaune; /* Notre couleur */
  char *texte;
  int long_texte; /* La taille du texte */

  /* récupère le texte */
  if ( !(texte = getenv("QUERY_STRING")) ) {
  	texte = "Erreur !";
	}
  long_texte = strlen(texte);

  /* On ouvre l'image initiale */
  if ( !(image_png_in = fopen ("bout_in.png", "rb")) ) {
  	fprintf (stderr, "Impossible de trouver l'image bout_in.png.\n\n");
  	exit (1);
  }
  image = gdImageCreateFromPng(image_png_in);

  /* On ferme le fichier de l'image. */
  fclose(image_png_in);

  /* On alloue une couleur. */
  jaune = gdImageColorResolve(image, 0xff, 0xff, 0);

  /*
   * On écrit le texte
   */
  gdImageString(image, gdFontMediumBold,
                (image->sx-long_texte*gdFontMediumBold->w)/2,
                (image->sy-gdFontMediumBold->h)/2,
                texte, jaune);

  /* Envoyer l'image au format PNG. */
  printf ("Content-type: image/png\n\n");
  gdImagePng(image, stdout);

  /* On détruit l'image. */
  gdImageDestroy(image);
  exit (0);
}
On compile en tapant
gcc -o expl_gd_3 expl_3.c -lgd -lpng
On place le binaire obtenu et un fichier contenant le bouton vide (bout_in.png) dans le répertoire cgi-bin de son serveur web.
Pour utiliser ce que l'on vient de faire, il suffit d'inclure dans un fichier html (ce fichier peut être généré par un cgi, vous me suivez ?), un tag img, en passant le texte au script en "query_string". Et Hop !
Exemple :
<html>
<body bgcolor="#ffffff" text="#000088">
<p><b>Mes boutons : </b></p>
<p><img src="/cgi-bin/expl_gd_3?Zazou"></p>
<p><img src="/cgi-bin/expl_gd_3?Xavier"></p>
<p><img src="/cgi-bin/expl_gd_3?alaide"></p>
</body>
</html>

On obtient alors une page semblable à celle-ci :
résultat du code...

Aller plus loin

J'ai présenté ici quelques fonctionnalités de la librairie gd. Cela vous aura, j'espère, donné envie de creuser plus avant par vous mêmes. D'autres fonctionnalités dont je n'ai pas parlé sont la copie de portions d'images, l'ouverture de portions d'images uniquement avec changement d'échelle à la volée, fonctionnalités très utiles pour se lancer dans la génération d'extraits de grosses images (photos aériennes ou satellitaires ou je ne sais quoi d'autre). Pour plus d'informations et pour la liste complète des fonctions de lalibrairie, consultez le fichier index.html de l'archive de gd ou la page officielle : http://www.boutell.com/gd/

J'ai placé les images, sources et toutes les archives nécessaires à la réalisation de ces exemples ainsi que les 3 binaires obtenus sur cette page. Attention, les versions des archives seront sans doute vite obsolètes mais ce sont celles utilisées pour cet article.

Pour info, si vous voulez tester les binaires, il vous faudra (en plus de la libc et ld-linux que vous avez) :

Comme d'habitude, j'ai "appris" ça en tapant ldd expl_gd_1, ldd expl_gd_2 et ldd expl_gd_3 dans ma console préférée.

N'hésitez pas à envoyer vos commentaires par mail en cliquant sur mon nom en haut de la page.
A bientôt, Xavier GARREAU (alaide)

Haut


Polices Freetype et gd-2.0

par Xavier GARREAU

Voici le deuxième article concernant gd. Il traite de l'utilisation de Freetype pour inclure du texte dans vos images et des nouveautés apportées à gd par la version 2.


Introduction

L'inclusion de textes dans les images est quelque chose de très utile, pour ne pas dire indispensable et cette facette de gd méritait bien un article à elle toute seule. Je parlerai donc ici de l'utilisation de la librairie freetype-2.x pour la génération d'images avec gd-2.0.1, en langage c. En effet, à l'heure où j'écris ces lignes la librairie gd-2 n'est pas encore stable mais bon, elle le sera bientôt et les seules nouveautés d'ici là ne seront que corrections d'éventuels bugs. Pour ce qui est du choix de ne traiter que la librairie freetype-2, il vient du fait que la version 2 de gd fait pareil. Les anciennes fonction liées à freetype-1 (celles qui se terminent par TTF) ne font que renvoyer vers celles liées à freetype-2 (celles qui se terminent par FT).
A l'issue de cette première partie de l'article, vous pourrez utiliser vos polices de windows à partir de programmes c sous linux, ainsi que toutes les autres polices truetype, freetype, ... (voir http://www.freetype.org pour plus d'infos)
La deuxième partie de cet article examinera les nouveautés de gd dans sa deuxième version.

Préparatifs

Compilation et installation de freetype-2

Téléchargez la dernière version de Freetype-2. Lors de l'écriture de l'article, il s'agissait de la version 2.0.3.
Voici comment on l'installe :
Placez vous dans le répertoire dans lequel vous avez téléchargé freetype puis tapez :
tar xvfy freetype-2.0.3.tar.bz2
cd freetype-2.0.3
make setup
make
su
make install
Cela installe freetype sous /usr/local. Vous pouvez ensuite redevenir "simple utilisateur".

Compilation et installation de gd

Pour avoir plus d'informations sur l'installation de gd, consultez le premier article sur gd, sur léa.

Placez vous dans le répertoire dans lequel vous avez téléchargé gd puis tapez

tar xvfz gd-2.0.1.tar.gz
cd gd-2.0.1
Editez le début du Makefile comme suit :
COMPILER=gcc
AR=ar
CFLAGS=-g -DHAVE_LIBPNG -DHAVE_LIBJPEG -DHAVE_LIBFREETYPE
LIBS=-lgd -lpng -lz -ljpeg -lfreetype -lm
INCLUDEDIRS=-I. -I/usr/local/include/freetype2
LIBDIRS=
INSTALL_LIB=/usr/local/lib
INSTALL_INCLUDE=/usr/local/include
INSTALL_BIN=/usr/local/bin
Puis tapez:
su
make install
Il ne s'agit pas d'une erreur de ma part, il faut bien taper directement make install, c'est comme ça !
Si vous obtenez une erreur du type : unresolved symbol (sur gdImageCreateFromPng par exemple) :

Utilisation

Pour les notions de base concernant gd, reportez vous à l'article précédent, celui-ci constitue une suite.

Exemple1

Dans cet exemple nous allons recréer l'image simple du premier exercice du premier article mais ajouterons dans la croix la mention santé écrite en Comics, par exemple, et ce, en jaune.
Il faut : La declaration de la fonction permettant d'écrire du texte en utilisant FreeType est la suivante :
char *gdImageStringFT(gdImagePtr im,
                            int *brect,
                            int color,
                            char *fontname,
                            double ptsize,
                            double angle,
                            int x, int y,
                            char *chaine)
où :


Le code :

/*
 * Fichier expl_gd2_1.c
 *
 * Pour les explications de base,
 * se reporter à l'article sur gd.
 */
#include <stdlib.h>
#include <gd.h>

// Deux macros qui simplifient la vie
#define brect_largeur (brect[4]-brect[0])
#define brect_hauteur (brect[1]-brect[5])

int main(void) {
  gdImagePtr image;
  FILE *image_png;
  char *err;
  int rouge, blanc, jaune, noir;
  char *chaine = "santé"; // La chaîne à écrire
  char *font = "/usr/share/ttf/comic.ttf"; // La police
  double taille = 20; // La taille de la police
  int brect[8]; // Les coordonnées du rectangle
  // entourant le texte entier.
  /* brect[0] X bas gauche
   * brect[1] Y bas gauche
   * brect[2] X bas droit
   * brect[3] Y bas droit
   * brect[4] X haut droit
   * brect[5] Y haut droit
   * brect[6] X haut gauche
   * brect[7] Y haut gauche
   */

  image = gdImageCreate(100, 100);
  blanc = gdImageColorAllocate(image, 255, 255, 255);
  rouge = gdImageColorAllocate(image, 255, 0, 0);
  jaune = gdImageColorAllocate(image, 255, 255, 0);
  noir = gdImageColorAllocate(image, 0, 0, 0);

  gdImageFilledRectangle(image, 20, 40, 80, 60, rouge);
  gdImageFilledRectangle(image, 40, 20, 60, 80, rouge);

  /* Les nouveautés commencent ici */
  /* On a droit à 60x20 pour placer notre chaîne
   * soit brect[4]-brect[0]<60 ET brect[1]-brect[5]<20
   * --> Pour récupérer brect sans écrire
   * le texte, on place im à NULL
   * Si on a un dépassement on réduit la 
   * taille de la police et on recommence
   */
  do {
    err = gdImageStringFT (NULL, brect,
                           jaune, font,
                           taille--,
                           0,
                           0,
                           0,
                           chaine);
    if (err) fprintf(stderr, "%s\n", err);
    fprintf (stderr, "Essai taille : %.0f\n", taille+1);
    fprintf (stderr,
             "* bas gauche ( %d, %d ), haut droite ( %d, %d )\n",
             brect[0], brect[1],
             brect[4], brect[5]);
    fprintf (stderr,
             "* largeur x hauteur : %dx%d\n",
             brect_largeur,
             brect_hauteur);
  } while ( ( brect_hauteur >= 20 ) || ( brect_largeur >= 60 ) );

  /* A décommenter pour voir le brect
  gdImageRectangle (image,
                    50-brect_largeur/2,
                    50-brect_hauteur/2,
                    50+brect_largeur/2,
                    50+brect_hauteur/2,
                    noir);
  */

  /* Une fois ici on a la bonne taille moins un.
   * Le milieu de l'image la moitié de la
   * largeur nous donne le x gauche.
   * Le milieu de l'image + la moitié de
   * la hauteur nous donne le y bas.
   * On retracnche la moitié des brect[0] et brect[1]
   * car on a vu qu'il ne valaient pas 
   * nécessairement 0 d'où un décalage
   */
  err = gdImageStringFT (image,
                         brect,
                         // mettre -jaune pour
                         // supprimer l'anti-aliasing
                         jaune,
                         font,
                         ++taille,
                         0,
                         50-(brect_largeur-brect[0])/2,
                         50+(brect_hauteur-brect[1])/2,
                         chaine);
  if (err) fprintf(stderr, "%s\n", err);

  /* Les nouveautés s'arrêtent ici */

  image_png = fopen("gd2_expl1.png", "w");
  gdImagePng(image, image_png);
  fclose(image_png);

  gdImageDestroy(image);

  exit (0);
}
On compile en tapant
gcc -o expl_gd2_1 expl_gd2_1.c -lgd -ljpeg -lpng -lfreetype
Après exécution par ./expl_gd2_1, vous obtenez dans le répertoire courant une image gd2_expl1.png, qui sera une des 4 présentées ci-dessous selon les modifications que vous aurez apportées au code :
 
Image normale Sans anti-aliasing
(-jaune)
résultat du code... résultat du code...
Avec dessin
du brect
Avec brect
Sans anti-aliasing
résultat du code... résultat du code...
Remarque : Dans une application réelle, une fois connue la bonne taille de police, vous enleveriez la boucle permettant de la trouver, vous ne traceriez pas le brect, etc ... Je l'ai fait ici dans un but pédagogique.

Exemple2 : Selon un angle ?

Nous allons maintenant écrire, selon un angle. Pas de gros changements ... On en profitera toutefois au passage pour faire connaissance avec le type gdPoint, utilisé pour tracer des polygones de façon aisée. Il nous servira pour tracer le brect de ce texte "en pente", grâce à la fonction gdImagePolygon.
On note qu'un type gdPoint a deux champs, x et y. C'est pas plus compliqué que ça !
Le code :
/*
 * Fichier expl_gd2_2.c
 *
 * Pour les explications de base,
 * se reporter à l'article sur gd.
 */
#include <stdlib.h>
#include <stdio.h>
#include <gd.h>
#include <math.h>

// Une macro pour transformer des degrés en radians
// NB: M_PI est définie dans math.h
#define en_radians(ndeg) (M_PI*(ndeg)/180)

// Deux macros qui simplifient la vie
#define brect_largeur (brect[4]-brect[0])
#define brect_hauteur (brect[1]-brect[5])

int main(void) {
  gdImagePtr image;
  FILE *image_png;
  char *err;
  int rouge, blanc, jaune, noir;
  char *chaine = "santé"; // La chaîne à écrire
  char *font = "/home/xavier/Docs/contribs_lea/c4/comic.ttf";
  double taille = 20; // La taille de la police
  int brect[8]; // Les coordonnées du rectangle
  // entourant le texte entier.
  gdPoint brect_points[4]; // Le tableau de points
  // pour tracer le polygone

  image = gdImageCreate(100, 100);
  blanc = gdImageColorAllocate(image, 255, 255, 255);
  rouge = gdImageColorAllocate(image, 255, 0, 0);
  jaune = gdImageColorAllocate(image, 255, 255, 0);
  noir = gdImageColorAllocate(image, 0, 0, 0);

  gdImageFilledRectangle(image, 20, 40, 80, 60, rouge);
  gdImageFilledRectangle(image, 40, 20, 60, 80, rouge);

  err = gdImageStringFT (NULL, brect,
                         jaune, font,
                         taille,
                         en_radians(45),
                         0,
                         0,
                         chaine);
  /* on stocke les points dans le tableau de gdPoints */
  brect_points[0].x = brect[0]+50-(brect_largeur-brect[0])/2;
  brect_points[0].y = brect[1]+50+(brect_hauteur-brect[1])/2;
  brect_points[1].x = brect[2]+50-(brect_largeur-brect[0])/2;
  brect_points[1].y = brect[3]+50+(brect_hauteur-brect[1])/2;
  brect_points[2].x = brect[4]+50-(brect_largeur-brect[0])/2;
  brect_points[2].y = brect[5]+50+(brect_hauteur-brect[1])/2;
  brect_points[3].x = brect[6]+50-(brect_largeur-brect[0])/2;
  brect_points[3].y = brect[7]+50+(brect_hauteur-brect[1])/2;

  /* On trace le polygone */
  gdImagePolygon (image, brect_points, 4, noir);

  /* On ajoute la chaîne de caractères
     comme avant, mais avec un angle */
  err = gdImageStringFT (image,
                         brect,
                         jaune,
                         font,
                         taille,
                         en_radians(45),
                         50-(brect_largeur-brect[0])/2,
                         50+(brect_hauteur-brect[1])/2,
                         chaine);
  if (err) fprintf(stderr, "%s\n", err);

  image_png = fopen("gd2_expl2.png", "w");
  gdImagePng(image, image_png);
  fclose(image_png);

  gdImageDestroy(image);

  exit (0);
}
On compile en tapant
gcc -o expl_gd2_2 expl_gd2_2.c -lgd -ljpeg -lpng -lfreetype
Après exécution par ./expl_gd2_2, vous obtenez dans le répertoire courant une image gd2_expl2.png.
résultat du code...

Quoi de neuf dans gd-2 ?

Ben oui !!! Vous devez bien vous le demander ! Alors voici ce que dit (en résumé) la section "what's new ?" de la page officielle pour la version 2.0.1 par rapport à la 1.8.3 du précédent article : Plus des améliorations et corrections que je vous conseille de lire directement sur la page, http://www.boutell.com/gd/
Notez que ce lien vous emmène pour l'instant sur la page de la version stable, qui, à l'heure où ces lignes sont écrites, est encore la 1.8.4. Il y a toutefois un (gros) lien vers la page des versions 2.0.x.

Exemple3 : Transparence

/*
 * Fichier expl_gd2_3.c
 *
 * Pour les explications de base,
 * se reporter à l'article sur gd.
 */
#include <stdlib.h>
#include <gd.h>

int main(void) {
  FILE *image_png;
  gdImagePtr image, image_d_avant;
  int orange, bleu;

  // On crée une image TrueColor
  image = gdImageCreateTrueColor(100, 100);
  // On alloue une couleur
  orange = gdTrueColor (255, 128, 0);
  // On trace un rectangleorange
  gdImageFilledRectangle(image, 0, 0, 100, 100, orange);

  // On ouvre l'image du premier exercice
  // et on en crée une image
  image_png = fopen("expl1.png", "r");
  image_d_avant = gdImageCreateFromPng (image_png);
  fclose (image_png);

  // on copie avec remise à l'échelle de 100x100 pixels
  // en partant de (0,0) de l'ancienne image
  // on place le résultat de taille 90x90 à
  // (5,5) dans la nouvelle
  gdImageCopyResampled(image, image_d_avant, 
    5, 5, 0, 0, 90, 90, 100, 100);
  // on détruit l'ancienne image en mémoire.
  gdImageDestroy (image_d_avant);

  // On alloue une couleur bleue semi transparente
  bleu = gdTrueColorAlpha(0, 0, 255, 
    gdAlphaTransparent / 2);
  // on se met en mode écrasement
  // La où on dessinera il y aura 
  // du bleu semi-transparent, c'est tout
  gdImageAlphaBlending(image, 0);
  // ontrca eun rectangle
  gdImageFilledRectangle(image, 10, 10, 90, 30, bleu);

  // On se met en mode "mélange"
  // Ce qu'on dessine, se mélange avec
  // ce qu'il y a dessous
  // 50% de bleu et 50% du dessous
  gdImageAlphaBlending(image, 1);
  gdImageFilledRectangle(image, 40, 10, 90, 90, bleu);

  image_png = fopen("gd2_expl3.png", "w");
  gdImagePng(image, image_png);
  fclose(image_png);

  gdImageDestroy(image);

  exit (0);
}
On compile en tapant
gcc -o expl_gd2_3 expl_gd2_3.c -lgd -ljpeg -lpng -lfreetype
On obtient après exécution, dans gimp :
résultat du code...
Notez : C'est tout pour cette fois !
N'hésitez pas à envoyer vos commentaires par mail en cliquant sur mon nom en haut de la page.
Retrouvez prochainement l'article remis en forme, les sources, archives et binaires sur mon site perso..., http://perso.club-internet.fr/xgarreau.
Vous pouvez également me contacter dans le forum Développement de Léa, je le regarde (très) souvent.
A bientôt.

Haut


Perl

par Jice et Jonesy

Installation et introduction à Perl.


Avant propos

Ceci n'est qu'une introduction au Perl, car nous pourrions écrire 4 ou 5 livres, voir plus, sur ce sujet ! ;-) Donc ne vous attendez pas à faire le tour de la question...
Et je précise aussi, que ce n'est pas non plus un cours de programmation : cet article s'adresse à des personnes possédant des bases de programmation et voulant se mettre à Perl.

Présentation

Perl est un langage de programmation extrêmement efficace pour traiter les fichiers et les chaînes de caractères. Venant du monde Unix, il intègre toutes les fonctions que l'on peut retrouver en shell avec des commandes comme grep, sed, awk, ... Sa syntaxe générale ressemble beaucoup à celle du C et du shell. De plus, il est très apprécié des administrateurs système afin de gérer les fichiers de log.

Note de Jice : Perl est un langage très polyvalent. LE jeu, j'ai nommé Frozen Bubble, est même programmé en Perl !

Les particularités de Perl


L'installation

Dans cette partie, je n'aborde que l'installation de Perl sur un système Linux, et ce quelque soit la distribution. Enfin, j'espère... ;-)
 

Vérifions si Perl est présent

Il y a des chances pour que Perl soit déjà installé avec notre distribution ou que celle-ci fournisse un package Perl. Pour vérifier si Perl est déjà sur notre système faisons, en console :
perl -v
Ceci doit nous retourner un message du genre:
This is perl, v5.6.1 built for i386-linux

Copyright 1987-2001, Larry Wall

Perl may be copied only under the terms of either the Artistic licence or the GNU General Public licence, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on this system using `man perl' or `perldoc perl'. If you have access to the Internet, point your browser at http://www.perl.com/, the Perl Home Page.

Si c'est le cas, nous pouvons sauter le paragraphe suivant.

Installation de Perl

Pas de chance... Donc allons voir si notre distribution fournit un package et si oui, installons-le. Sinon...
Les binaires sont accessibles ici : http://www.activestate.com/Products/Download/Get.plex?id=ActivePerl&_x=1
Note : je viens de remarquer que maintenant il demande e-mail, prénom et nom pour télécharger Perl.
Les sources sont accessibles ici : http://www.perl.com/pub/a/langage/info/software.html#sourcecode

Maintenant détarons le package
tar -xvzf <votre package>
Allons sous le répertoire créé.

Si nous souhaitons choisir nos paramètres en répondant à des questions :
./Configure -Dprefix=/usr
make
En root (su)
make install

Sinon, pour une installation par défaut :
./Configure -Dprefix=/usr -d -e
make
En root (su)
make install

Vérifions que Perl est bien installé
perl -v


Introduction au langage Perl

Les principales règles du Perl

Définir l'interpréteur

Le fait de définir l'interpréteur dans le script permet de l'exécuter directement. Pour ce faire il faut placer la ligne suivante au tout début du script :
#!/usr/bin/perl
Ce qui permettra d'exécuter le script directement par :
./script.pl
Sans cette ligne il faudrait exécuter le script comme ceci :
perl script.pl
En ajoutant l'option -W, nous obtenons la liste des toutes les erreurs et de tous les avertissements lors de l'interprétation du script.
 

Les commentaires

Un commentaire débute par un caractère dièse (#). Tout ce qui suit ce caractère jusqu'à la fin de la ligne sera considéré comme du commentaire.
Par exemple :
# Ceci est un exemple de commentaire
print "test"; # un 2ème commentaire
 

Utiliser une libraire particulière

L'extension des fichiers librairies (ou module) est pm. pm signifie Perl Module.
Pour appeler une librairie, on ajoute la directive :
use la_librairie;
Pour indiquer à perl où chercher nos librairies personnelles, on utilise la directive :
use lib "le chemin d'accès";

Remarque : les chemins de recherche des librairies sont définit dans le tableau @INC
 

Déclarer une variable

Variable globale :
Cela se fait automatiquement lors de la première utilisation de la variable. Cependant, lorsqu'on utilise use strict, il faut déclarer les variables globales par la directive suivante : use vars qw($var1 $var2 @tab1 %hash1);

Remarque : le mot clef local s'applique à une variable globale (local $variable;). Il permet à l'intérieur d'un bloc de cacher une variable globale par une autre valeur. La variable reste globale, c'est à dire que dans les procédures appelées dans le bloc, la nouvelle valeur est utilisée ; à la fin du bloc, l'ancienne valeur est restaurée.

Variable lexicale :
Une variable lexicale n'existe qu'à l'intérieur d'un bloc. On parle aussi de variable locale.
On la déclare par my $variable ou my @tableau...

Pour déclarer plusieurs variables et un tableau en une seule commande, faire :
my ($var1, $var2, @tab);
 

Les opérateurs

Voici les opérateurs les plus couramment utilisés. Pour plus d'info sur les opérateurs : man perlop
Opérateurs unaires
! : négation logique. Si $a vaut vrai, !$a vaudra faux.
- : négation arithmétique. Si $a vaut 2, -$a vaudra -2.
~ : négation binaire ou complément à 1.
++ : incrémentation ; si ++ est placé avant la variable, il la décrémente avant de retourner la valeur, s'il est placé après, il retourne la valeur puis l'incrémente. Exemple : $A++
-- : décrémentation ; idem.
Opérateurs binaires
+ - * / % ** : opérations arithmétiques (% est le modulo, ** est l'exponentiation).
. : concatène deux chaînes : "aze"."rty" vaut "azerty".
x : "multiplie" une chaîne : "to" x 2 vaut "toto".
& | ^ << >> : opérations sur les bits : et, ou, ou exclusif (xor), décalage à gauche, à droite.
&& || : et logique, ou logique.
Opérateur ternaire
?:  : cet opérateur particulier est nommé "opérateur conditionnel". Il fonctionne à la manière d'un si alors sinon. Si l'expression à gauche du ? est vraie, l'argument avant le : est retourné, sinon l'argument après le : est retourné. Mieux vaut un exemple :
print ($n>1)?'s':'';
affichera 's' si $n est plus grand que 1, et n'affichera rien ('') sinon.
Opérateurs de comparaison numériques
< <= > >= : plus petit, plus petit ou égal, plus grand, plus grand ou égal
== != : égal, différent
Opérateurs de comparaison pour les chaînes de caractères
lt gt le ge : plus petit, plus petit ou égal, plus grand, plus grand ou égal
eq ne : égal, différent
=~ !~ : teste si une chaîne de caractères correspond à une expression régulière. Voir le paragraphe expressions régulières.

L'affectation d'une variable

L'opérateur d'affectation "normal" est '='. Par exemple :
$variable = mavaleur;
affecte la valeur mavaleur à la variable $variable.

D'autres opérateurs d'affectation sont un raccourci pour les opérations binaires.
Par exemple $a += 2 est un raccourci pour $a = $a + 2.

Les opérateurs d'affectation les plus couramment utilisés sont :
  = : affectation "standard"
 +=  -=  *=  /=   %= : pour les opérations arithmétiques (+, -, *, / et modulo)
&&= ||=  != : pour les opérations logiques (et logique, ou logique, non logique)
 &=  |=  ^=  ~=  <<=  >>= : pour les opérations logiques sur les bits (et, ou, ou exclusif, complément à 1, décalages gauche et droite)
 .= : pour les opérations sur les chaînes de caractères (concaténation)
 =~  !~ : pour les expressions régulières (voir plus bas).

Quelques variables particulières prédéfinies

use English; permet d'utiliser les noms des variables au lieu des noms standards des variables spéciales (ex : $ARG au lieu de $_).

@_ ou @ARG : Tableau d'arguments. Sert pour le passage de paramètres.
$_ ou $ARG : Variable par défaut des fonctions unaires entrées/sorties, recherche...
$/ ou $RS ou $INPUT_RECORD_SEPARATOR : fin de ligne lorsqu'on lit un fichier, \n par défaut sous unix et \r\n sous windows.
$! ou $ERRNO : suivant le contexte d'utilisation, retourne le numéro de la dernière erreur système, ou le message d'erreur système.
$0 ou $PROGRAM_NAME : Nom du fichier qui contient le script Perl.
$^0 ou $OSNAME : Nom du système d'exploitation.
Utiliser une variable d'environnement
$variable = $ENV{"nom de la variable d'environnement"}; Remarque, ce n'est que l'utilisation du hachage %ENV.

Pour plus d'information sur ces variables : man perlvar
 

Branchements conditionnels

Ce test classique peut s'écrire de la manière suivante :
if (test) {bloc}
if (test) {bloc} else {bloc}
if (test) {bloc} elsif (test) {bloc} ... else {bloc}

test étant toute expression qui retourne une valeur.
exemple :
if (!open(F,$FICHIER)) { die "impossible d'ouvrir $FICHIER: $!"; }

Il y a d'autres manières de faire la même chose, vous pouvez utiliser par exemple :
 
instruction if test; exécute l'instruction si le test est vrai.
exemple : die "impossible d'ouvrir $FICHIER: $!" if !open(F,$FICHIER);
instruction unless test; exécute l'instruction si le test est faux.
exemple : die "impossible d'ouvrir $FICHIER: $!" unless open(F,$FICHIER);
commande1 or commande2; exécute la commande1 ; si elle retourne faux, alors la commande2 sera exécutée. Sinon on passe à la suite.
exemple : open(F,$FICHIER) or die "impossible d'ouvrir $FICHIER: $!";
test?commande1:commande2; exécute la commande1 si la condition du test est remplie, sinon il exécute la commande2.

Note : Pour ceux qui connaissent, il n'y a pas d'équivalent au 'case' ou 'switch'. Il y a plusieurs façons de faire, notamment utiliser if elsif ... else.
man perlsyn vous donnera plus d'informations.
 

Les boucles

For et foreach
Il y a deux types de boucles for, le for 'style C', et le for/foreach Perl.
for/foreach Perl
Dans ce cas, for et foreach sont synonymes. On peut utiliser l'un ou l'autre pour améliorer la lecture, par habitude, etc.

Pour aller de 1 à 9 :
for $cpt (1 .. 9) {bloc} ou foreach $cpt (1 .. 9) {bloc}
(dans {bloc}, $cpt prend successivement les valeurs de 1 à 9)
ou
for (1 .. 9) {bloc}
ici, à chaque itération, la variable par défaut $_ prend la valeur suivante.

Pour parcourir chaque éléments d'un tableau :
foreach $variable (@tableau) {bloc}
A chaque itération, $variable prendra la valeur suivante de @tableau.
Si on omet $variable, alors la valeur de l'élément sera stocké dans la variable $_ :
foreach (@tableau) {bloc}

Remarque : l'écriture 1..9 définit en réalité un tableau de 9 éléments (1,2,3,4,5,6,7,8,9).

for 'style C'
La construction 'classique' à la C est possible, mais tellement moins Perl...
for ($cpt=1 ; $cpt < 10 ; $cpt++) {bloc}
Cette construction est néanmoins très puissante, et permet de faire beaucoup plus de choses que de simples itérations... A vous de voir !

A noter que dans les exemples ci-dessus, les variables $cpt ou $variable sont locales à la boucle. Si elles doivent être déclarées, vous pouvez les faire précéder du mot clef my :
for my $cpt (1 .. 9) {bloc}
for (my $cpt=1 ; $cpt < 10 ; $cpt++) {bloc}

While
Les boucles while se construisent comme suit :
while (test) {bloc1}
while (test) {bloc1} continue {bloc2}

Le bloc1 puis le bloc2 sera exécuté tant que le test est vrai.

L'exécution des boucles peut être pilotée à l'intérieur des blocs par les commandes next, last et redo.

Par exemple, le code suivant lit l'entrée standard, et ignore les commentaires (lignes commençant par #)
while (<STDIN>) {
    next if /^#/;
    ...
}
Autre exemple, une itération de 1 à 9
$cpt = 1;
while ($cpt < 10) {
  ...
  $cpt++;
}
ou bien :
$cpt = 1;
while ($cpt < 10) {
  ...
}
continue {
  $cpt++;
}
Pour plus d'information sur ces constructions : man perlsyn.
 

Les fonctions et procédures

Déclaration des fonctions
Une fonction peut être déclarée n'importe où dans le script Perl, mais pour des questions de lisibilité, il est préférable de déclarer les fonctions regroupées soit en début, soit en fin de script.
On peut pré-déclarer les fonctions par :
sub mafonction;
ou les déclarer directement par :
sub mafonction {bloc}
Passage de paramètres
Les paramètres sont passés comme une liste de valeurs scalaires (nombre, chaîne...) à plat.
Par exemple :
ma_fonction($variable, "chaîne");

Pour lire valeur des paramètres passés dans la fonction, il suffit de lire la variable @_ qui est le tableau contenant les paramètres.
Une façon simple de les lire est d'écrire en début de fonction :
my ($variable, $chaine) = @_;
On peut aussi récupérer les arguments en dépilant les éléments du tableau @_ avec la commande shift.
my $variable = shift @_;
qui peut s'abréger en
my $variable = shift;
car le tableau @_ est l'argument par défaut de shift.

Les paramètres sont passés par valeur. Pour pouvoir modifier le contenu d'une variable passée en paramètre à une fonction, il faut passer à cette fonction une référence sur la variable (on parle de passage par référence). Une référence est un type scalaire particulier qui permet de se référer à une variable. La notation consiste à faire précéder la variable par un caractère '\'. Pour récupérer la variable cachée derrière la référence (ou déréférencer), il faut faire précéder la référence du caractère de type correspondant, et optionnellement entourer la référence d'accolades.
Exemple :
$a = 1;
$b = \$a;       # $b est une référence à $a
$c = $$b + 1;   # $c = 2
$c = ${$b} + 1; # synonyme

Démonstration :
sub test1 {
    my $a=shift; # récupération du paramètre
    $a += 1;     # on ajoute un
}
sub test2 {
    my $ref_a=shift;
    $$ref_a += 1; # le premier $ déréférence la référence
}
$a = 1;
print $a;   # affiche 1
test1($a);
print $a;   # affiche 1
test2(\$a); # \$a est une référence sur $a
print $a;   # affiche 2

Le passage par référence a aussi pour d'intérêt de pouvoir passer des tableaux ou des hachages en paramètres à une fonction. En effet, on a vu que les paramètres d'une fonction sont passés comme une liste de valeurs scalaires à plat. Si on désire passer 2 tableaux @tab1=(1,1,1) et @tab2=(2,2) à une fonction, et que l'on écrit :
mafonction (@tab1, @tab2);
alors perl traduira cela comme :
mafonction (1,1,1,2,2);
car il met toutes les valeurs à plat dans une liste. Une fois dans la fonction on sera bien embêté pour savoir distinguer les éléments du premier tableau de ceux du deuxième.
On va donc passer une référence sur chaque tableau :
mafonction (\@tab1, \@tab2);

Exemple :
sub liste_gt {
    my ($l1ref, $l2ref) = @_;
    $n1 = @$l1ref; # @$l1ref représente le tableau référencé par $l1ref
    $n2 = @$l2ref;
    # (affecter un tableau dans un scalaire retourne le nombre d'éléments)
    if ($n1 > $n2) { return 1; }
    else { return 0; }
}
@tab1 = (1,2,3);
@tab2 = ("a",2,"r",5);
if (liste_gt(\@tab1, \@tab2)) {
    print "tab1 a plus d'éléments que tab2\n";
} else {
    print "tab2 a plus d'éléments que tab1\n";
}

Tout cela s'applique de la même façon aux hachages.
Pour plus d'informations sur les références, voir man perlreftut (tutoriel) et man perlref (références).

Retour de valeurs
Une fonction peut renvoyer une ou des valeurs, grâce à la commande return :
sub ecrire
{
  ...
  return $valeur;
  ...      # renvoie une valeur unique
  return $valeur1, $valeur2;
  ...      # renvoie une liste de valeurs
}
Cette commande provoque la sortie immédiate du sous-programme pour retourner dans le programme appelant.

return permet de renvoyer une valeur ou une liste de valeurs. Cela permet donc à une fonction de retourner soit un tableau :
@resultat = ma_fonction;
soit plusieurs valeurs :
($a, $b) = ma_fonction;

De même que pour le passage de paramètres, la liste de valeur est une liste "à plat", ce qui signifie que pour retourner plusieurs tableaux, il faut également passer par les références :
($ref_a, $ref_b) = ma_fonction;
$ref_a est une référence sur le tableau @$ref_a et où ma_fonction retourne une liste de deux références.
 

Pour plus d'information sur les fonctions de Perl, voir man perlsub.
 
 

Les expressions régulières, ou regex

Perl est un langage très pratique pour manipuler du texte. Un des éléments qui le rend si adapté à cet usage est la présence des expressions régulières, qui permettent de déterminer si une chaîne de caractère correspond à un masque prédéfini, d'extraire des éléments d'une chaîne de caractères suivant un masque, de remplacer des parties d'une chaîne suivant un masque, etc. C'est ce masque, défini sous forme d'une chaîne de caractères, que l'on appelle expression régulière.

Par exemple, $a =~ /lea/ teste si la chaîne "lea" est dans la chaîne $a, $a =~ /^lea/ si $a commence par "lea", $a =~ /l[eé]a/ si la chaîne $a contient "lea" ou "léa"...

De prime abord, ce sont des sortes de formules qui semblent ne rien vouloir dire, mais une fois que nous les maîtrisons, elles se révèlent extrêmement pratiques et puissantes.

Les éléments composant une expression régulière
Les éléments les plus utilisés sont :
Les différentes utilisations des expressions régulières
m/regex/ ou /regex/ (sans le m) teste si une chaîne correspond (match) à une regex donnée.
s/regex/substitution/ remplace la partie de la chaîne correspondant à la regex par la substitution.

Pour appliquer un test m// ou une substitution s/// à une chaîne de caractères, on utilise l'opérateur =~ ou !~.
Par exemple :
if ($s =~ /win(dows)?/) { kickban(); }
teste si la chaîne $s contient "win" ou "windows".
Si on ne précise pas de chaîne, le test se fait sur la variable par défaut $_ :
if (/win(dows)?/) { kickban(); }
teste si $_ contient "win" ou "windows".
L'opérateur !~ correspond quant à lui à la négation : $s !~ /coin/ teste si la chaîne $s ne contient pas "coin".

options
Les expressions régulières peuvent de plus être suivies de modifieurs ou options, qui en modifient le comportement, notamment : Par exemple :
"Bonjour Léa" =~ m/jour/; # correspond
"Bonjour Léa" =~ m/Jour/; # ne correspond pas
"Bonjour Léa" =~ m/Jour/i; # correspond
groupement et capture
Si une partie d'une regex est entre parenthèses, alors la partie de la chaîne qui correspond à cette parenthèse sera affectée à la variable $1 (on dit "capturée"). Si une deuxième parenthèse est présente dans la regex, la variable $2 sera utilisée, et ainsi de suite. Si la parenthèse commence par ?: alors il n'y aura pas d'affectation à une variable $1, $2, etc.
Ainsi :
if (/win(?:dows)?\s(..)/i) {
  if ($1 eq "xp") { kickban(); }
  else { kick(); }
}
teste si $_ contient "win" ou "windows", suivi d'un espace, puis de 2 caractères, que l'on va capturer dans $1. La première parenthèse (?:dows) permet de regrouper les quatre caractères "dows" afin de leur appliquer le quantifieur '?' (0 ou 1).

Les captures $1, $2... peuvent être utilisées lors d'une substitution :
$s =~ /win(?:dows)?\s(..)/winblows $1/
 

Tester si une chaîne correspond à un masque donné : m// ou //
Par exemple, pour tester si une chaîne contient le mot "lea" (le i de la fin permet de ne pas différencier majuscules et minuscules) :
if (/lea/i) ...

Pour tester si une chaîne commence par la lettre 'a', on peut faire :
if (/^a/) ...

Pour tester si une chaîne se termine par 'z' :
if (/z$/) ...

Enfin pour tester si une chaîne contient un quadruplet héxadécimal (i.e. 4 chiffres de 0 à 9 ou A à F) :
if (/[0-9A-F]{4}/) ...

Ici, par défaut, c'est la valeur contenu dans $_ qui est utilisée pour le test. Et bien entendu, nous pouvons compliquer à loisir l'expression régulière, et utiliser le groupement (voir plus haut) afin de récupérer les valeurs trouvées dans les chaînes pour s'en servir par la suite.
 

Substituer une partie d'une chaîne à une autre : s///
Rappel : la syntaxe est s/regex décrivant l'élément à remplacer/élément remplaçant/options

Exemples :
$variable =~ s/a/b/;
Cette commande va remplacer le premier 'a' de la ligne par 'b'. Pour remplacer tous les 'a' faire :
$variable =~ s/a/b/g;
Si en plus nous voulons que cela ne fasse pas la différence entre les majuscules et les minuscules (case sensitive), faire
$variable =~ s/a/b/gi;
Pour remplacer tous les ensembles de caractères commençant par 'a', finissant par 'e' et avec au moins un caractère au milieu sans être sensitif par 'toto', faire :
$variable =~ s/a.*e/toto/gi;
 

La traduction : tr///
Cette construction "à la regex" permet de remplacer des caractères par d'autres dans une chaîne, à la manière de la commande unix 'tr'.
La syntaxe est tr/éléments à traduire/éléments traduits/
Ou y/éléments à traduire/éléments traduits/

Exemple :
$variable =~ tr/a-b/A-B/;
Cela va transformer tous les 'a' et 'b' minuscules en majuscules.

Plus d'info sur :
man perlre
man perlretut (le tutoriel sur les regex, une bonne lecture !)
 

Structures de données complexes

Tableaux et tables de hachage multidimensionnels
On a vu plus haut que les tableaux Perl ne possèdent qu'une seule dimension (il ne peuvent contenir que des éléments de type scalaire). On a aussi vu plus haut l'existence de références, type scalaire particulier qui permet de se référer à une variable, donc en l'occurence à un tableau.
Ainsi pour représenter la matrice
 0 1 2
 3 4 5
 6 7 8
Nous allons pouvoir faire :
@ligne1 = (0, 1, 2);
@ligne2 = (3, 4, 5);
@ligne3 = (6, 7, 8);
@matrice = (\@ligne1, \@ligne2, \@ligne3);
Perl introduit une nouvelle notation pour ce faire : au lieu d'entourer la liste avec des parenthèses, nous allons le faire avec des crochets, ce qui aura pour effet de retourner une référence :
$ligne1_ref = [0, 1, 2];
etc...
ou plus simplement :
@matrice = (
 [0, 1, 2],
 [3, 4, 5],
 [6, 7, 8],
);
Dans tous ces cas, @matrice est un tableau de trois éléments, qui sont chacun une référence vers un autre tableau. $matrice[1] est une référence sur la 2ème ligne de la matrice.

Afin d'accéder aux éléments de la matrice, nous pouvons utiliser une nouvelle notation : la flèche '->'.
En effet, afin d'accéder au 3ème élément de $ligne1_ref définie plus haut, nous utiliserons $ligne1_ref->[2] (ce qui est synonyme de ${$ligne1_ref}[2].)
Ainsi, afin d'accéder au '5' dans notre matrice, nous ferons : $matrice[1]->[2].
Mais... Perl est gentil avec nous, car entre deux indices de tableau, la flèche est optionnelle ! Donc nous pouvons écrire : $matrice[1][2], ce qui ressemble maintenant à un tableau bi-dimensionnel.

De même, si $hash_ref est une référence vers un hachage, $hash_ref->{'nom'} renverra l'élément du hachage indexé par 'nom'.

L'ensemble tableaux, hachages et références peut bien sûr être mixé dans l'ordre qui vous plaira. Par exemple, pour réaliser un tableau de personnes, nous écrirons :
@staff = (
 [ prenom => "jice", nom => "cardot" ],
 [ prenom => "fred", nom => "bonnaud" ],
 [ prenom => "serge", nom => "tchesmeli" ],
);
et pour accéder au prénom de la première personne, nous ferons : $staff[0]->{'prenom'}(meuh non je ne suis pas mégalo ;) ou $staff[0]{'prenom'} car ici aussi la flèche est optionnelle.

Notes :

Les références symboliques

On a vu ci-dessus une utilisation des références, pour le passage et le retour de valeurs dans les fonctions. On vient de voir également leur utilité pour travailler avec des structures de données complexes.

Les références peuvent aussi servir dans un autre contexte : si vous déréférencez une variable contenant une valeur scalaire plutôt qu'un référence vers une autre variable, elle sera considérée comme une référence symbolique, c'est à dire que la valeur scalaire sera utilisée en tant que nom de la variable.
Exemple :
$test = "toto";
$$test = 144;     # affecte 144 à $toto
${$test} = 12;    # affecte 12 à $toto
$test->[1] = 12;  # affecte 12 à $toto[1]
@$test = (1,2,3); # affecte le tableau @toto

Cela peut aller beaucoup plus loin, car vous pouvez mettre une chaîne de caractères entre les accolades :
${$test x 2} = 12;   # affecte 12 à $totototo
$titi = 2;
${"$toto$titi"} = 2; # affecte 2 à $toto2
etc.

Remarque : l'utilisation des références symboliques avec use strict; (tel que préconisé au début de l'article) n'est pas possible. Pour contourner ce problème, il faut appeler l'instruction no strict 'refs'; dans le bloc où on veut utiliser les références symboliques.

Autre exemple d'utilisation :
$min = 12;
$max = 144;
while (<FICHIER>) {
  if (/^([A-Z]+)\ (\d+)\ (\d+)\ (\d+)$/) {
    # les masques ont affecté $1, $2, $3 et $4 (voir groupement/capture)
    print "$1: ";
    # on va effectuer une même opération sur $2, $3 et $4 :
    for $i (2 .. 4) {
      no strict 'refs';
      # on utilise les références symboliques : si $i = 2, alors $$i est $2
      if ($$i > $max) print "sup ";
      elseif ($$i < $min) print "inf ";
      else print "ok ";
    }
  }
}

Manipulation de fichiers

Comme toujours en Perl, il y a plusieurs moyens de faire une même opération.
Ouverture "à la shell" :
Cette façon de faire offrant un moyen simple d'ouvrir un fichier en lecture / écriture est empruntée du shell.
open(FILE, "< nom du fichier"); # ouvre en lecture
open(FILE, "  nom du fichier"); # idem
open(FILE, "> nom du fichier'); # ouvre en écriture
open(FILE, ">>nom du fichier'); # ouvre en ajout

Remarque : le fichier "moins" correspond à l'entrée standard stdin en lecture et à la sortie standard stdout en écriture. Par exemple  open(SORTIE,">-") ouvre la sortie standard.

Ouverture "à la C" :
La fonction sysopen de Perl correspond à l'appel système open(2) du C. Cela permet une plus grande maîtrise sur l'ouverture des fichiers, au prix d'une plus grande complexité.
Synoptique : sysopen DESCRIPTEUR, CHEMIN, FLAGS, [MASQUE]
MASQUE correspond au masque octal des permissions du fichier (utile pour la création d'un fichier), combiné avec l'umask du processus perl. voir l'article sur les permissions.
Je vous renvoie vers un cours de C pour la signification des exemples suivants, et vous donne la correspondance avec l'open "à la shell".
 
à la Shell à la C
open(F, "< $chemin"); sysopen(F, $chemin, O_RDONLY);
open(F, "> $chemin"); sysopen(F, $chemin, O_WRONLY | O_TRUNC | O_CREAT);
open(F, ">> $chemin"); sysopen(F, $chemin, O_WRONLY | O_APPEND | O_CREAT);

Bien sûr, sysopen permet de faire plus de choses que le simple open(), en jouant sur les flags ci-dessus et O_RDWR, O_RDONLY, O_EXCL, O_NONBLOCK, O_BINARY. Pour plus d'informations, voir man perlopentut ou man 2 open.

Fermer un fichier
close(FILE);
FILE étant le descripteur du fichier ouvert.
Descripteurs de fichier spéciaux
STDIN et STDOUT sont définis dès le lancement de votre programme. Ils correspondent respectivement à l'entrée et à la sortie standard. Rien ne vous empêche de les redéfinir, en les ouvrant sur un autre fichier, par exemple : open(STDIN, "mon_fichier");
Lire dans un fichier (l'opérateur diamant)
À chaque appel, <DESCRIPTEUR DE FICHIER> (on appelle <> l'opérateur "diamant") retourne la ligne suivante du fichier (en fait jusqu'à la fin de ligne définie par $/, voir le paragraphe sur les variables prédéfinies.), ou renvoie faux en fin de fichier.
exemple :
open (F, "mon_fichier");
$premiere_ligne = <F>;
while (<F>) {
  ... traitement en fonction de $_ ...
}
close(F);

exemple : pour lire tout un fichier dans une variable :
open (F, "mon_fichier");
undef $/;
$contenu_fichier = <F>;
close(F);

La lecture sur l'entrée standard est extrêmement simplifiée, car STDIN est déjà ouvert, et <STDIN> et <> sont synonymes. Ainsi, il suffit de faire :
while (<>) {
  ... traitement de la ligne en cours ...
}

Écrire dans un fichier
Il suffit de spécifier à la commande print ou printf le descripteur de fichier à utiliser.
Exemple :
open(FLOG, ">> fichier.log");
print FLOG "nouveau log\n";
close(FLOG);
 

Quelques autres commandes utiles

voir man perlfunc pour la liste exhaustive des commandes intégrées de Perl.

split

split correspond en partie à la fonction cut en shell. Elle permet de découper une chaîne de caractères.
Exemple :
($variable, @tableau) = split (":", "prenom:nom:age:adresse:ville:telephone");
Renvoie "prenom" dans $variable et le reste dans des cases distinctes du tableau @tableau.

systeme ou exec

exec permet de lancer un programme extérieur.
system de même que exec, mais produit un fork, processus fils.
Exemple :
@args = ("ls", "-lrt", "toto*");
system(@args);
Cela va lancer la commande :
ls -lrt toto*
Attention, l'utilisation de ces commandes rend votre script beaucoup moins portable ! En général, il y a moyen de tout faire en Perl, il faut éviter de recourir à ces fonctions.

chomp

chomp supprime tous les caractères de fin de ligne d'une chaîne de caractères et renvoie le nombre de caractères supprimés.
Exemple :
$nb_car = chomp($chaine);
Contrairement à chop qui enlève seulement le dernier caractère de fin de ligne.
 

Exemple d'un script Perl

Le script va se lire lui-même et sortir à l'écran son contenu modifié :
#!/usr/bin/perl -W
# Fichier script.pl
use strict qw(subs vars refs);

my $file = "$0";

open(FILE, "<$file");

#Lecture du fichier ligne par ligne jusqu'à la fin du fichier.
while (<FILE>) {
  #Remplace tous les mots 'file' par 'fichier'.
  $_ =~ s/file/fichier/ig;
  #Transforme les lettres minuscules de 'a' à 'e' en majuscules.
  $_ =~ tr/a-e/A-E/;
  #Affiche le résultat des transformations à l'écran.
  print $_;
}

close(FILE);
#Fin script.pl
Ce script est totalement inutile mais je crois que cela montre la facilité d'utilisation.
Oui, j'en conviens, en shell cela tiens en une ligne : cat script.pl | sed -e 's/file/fichier/ig' | sed y/a-e/A-E/. Bizarre, cela ressemble au Perl !? ;-)


Obtenir de l'aide ou en savoir plus

Sur notre machine

Et oui ! Sur notre propre machine il y a plein de sources de documentation sans même le savoir.
La première qui viens à l'esprit naturellement c'est LE man.
man perl vous donne quelques généralités, ainsi que la liste de toutes les sections du manuel de perl (perlsyn, perlre, perlvar, perlop, perlfun...).

Ensuite, il y a le man propre à Perl, qui est perldoc. Le meilleur moyen de voir comment cela marche, c'est d'essayer :
perldoc perldoc

Bon, l'inconvénient, c'est que tout ceci est en anglais...

Sur Internet

Sur Internet, nous trouverons plein de sites parlant de Perl. En général, ils parlent des scripts CGI (scripts Perl pour serveur web). Mais néanmoins, il y a Sinon, si on a les moyens les livres O'Reilly sur Perl sont très bien écrits et sont la référence en la matière.
 

Le mot de la fin

Surtout n'hésitez pas à m'envoyer vos remarques, vos corrections et vos idées, merci.

Haut


Utilisation de Java grâce à Jpackage.org

par Mickael

Installez proprement Java sur une distribution à base de RPM

Le Java, saimalsaiproprioetsaipalibre, mais souvent on a besoin de l'utiliser, pour tout un tas de bonnes raisons. De plus, il existe de très bons logiciels libres écrits en Java, et il fait partie des langages les plus utilisés par l'ASF ( Apache Software Fundation. Malheureusement, il y a rarement des paquets de logiciels Java, car il nécessite une JVM, une machine virtuelle Java, une espèce d'interpréteur de code assembleur d'un processeur qui n'existe pas ( voir Wikipedia pour une description plus détaillée et sans doute plus claire ).

Et c'est précisément la le problème, la plupart des JVMs ne sont pas libres, donc les distributions ne les incluent pas. Quand aux JVMs libres, elles ne sont pas assez performantes, aussi bien au niveau de la rapidité d'éxecution que du support du language. Quand un distributeur inclue un paquet de JVM, il met peu de programmes Java qui pourraient en bénéficier, et c'est dommage.

C'est la qu'intervient le projet Jpackage. Il s'agit d'un projet de distribution de RPMs de logiciels Java pour plusieurs distributions. Grâce à eux, installer Tomcat ou Jedit revient à simplement taper urpmi jedit ou yum install tomcat4. Ils proposent des paquets pour Mandrakelinux, Red Hat, Fedora, et d'autres distributions ( mais non testées ). Une fois le projet ajouté parmi vos sources de RPMs, vous pouvez donc accéder à eclipse, à ant, et autres logiciels Java habituellement plus complexes à installer.

Toutefois, il reste le problème de la JVM. Malgré les efforts du projet et les tentatives de prise de contacts, Sun ( et les autres comme IBM, etc ) refusent de laisser des packageurs externes refaire leurs RPMs. Une solution a du être élaborée par les membres de Jpackage, que je vais expliquer dans ce document.

La problématique de base est la suivante : "Comment garder la cohérence du système de paquets lors qu'on veut utiliser des logiciels dans des paquets incorrects, non normalisés, ou inexistants ?". La réponse trouvée est de faire ou refaire les paquets. Cela permet de garantir une intégration correcte avec la distribution, d'être sur qu'on les retire sans problème, et d'être sur que tout ne seras pas cassé le jour ou le distributeur change tout, comme Sun semble le faire si souvent. Vous trouverez plus d'explications dans la FAQ du projet.

Ce document a été fait en testant sur une distribution Mandrakelinux 10.0, mais devrait être suffisamment générique pour d'autres distributions. N'hésitez à me faire parvenir vos contributions.

Mise en oeuvre général

Vous l'aurez compris, nous allons donc refaire les RPMs du JDK ( Jave Developer Kit, une JVM + un compilateur Java ) afin de pouvoir utiliser jpackage. Le déroulement est le suivant :

Le but à la fin étant de pouvoir utiliser urpmi ( ou yum, ou apt ) pour installer sans problème un logiciel comme jext.

Préparation du home pour la recompilation de RPM

Sans rentrer dans les détails, il existe deux types de RPM. Les RPM binaires, qu'on voit souvent, qui contiennent les logiciels compilés et prêts à l'emploi, et les RPM sources, qui sont utilisés pour faire les RPMs binaires. Un fichier RPM source, ou src.RPM est un RPM qui contient les sources d'un programme, plus des patches, d'autres fichiers, et un fichier de spécification RPM, appelé spec ( car son extension est .spec ). La moitié du travail pour faire un RPM consiste à écrire ce fichier, l'autre étant de faire marcher le spec comme il faut, et la troisième moitié étant de le tester, de coller aux règles de la distribution et de faire le support utilisateur et correction de bugs.

Pour faire un RPM, il existe quelques documentations ( RPM.org, qa.mandrakesoft.com, linuxfrench.net ), mais pour le cas qui nous intéresse ( une "simple" recompilation ), seul la partie préparation nous importe.

Pour résumer ces documents, il faut créer une arborescence spéciale destinée aux opérations de RPM dans votre répertoire personel et dire à RPM d'utiliser ces dossiers :

$ mkdir -p ~/rpm/{RPMS/{i586,noarch},SRPMS,SPECS,tmp,BUILD,SOURCES}
$ cat << EOF > ~/.rpmmacros
 %_topdir               $HOME/rpm
 %_tmppath              /tmp
EOF

La dernière chose à faire, c'est d'installer le paquet rpm-build, afin d'avoir les fichiers pour recompiler un RPM.

# urpmi rpm-build

Récupération des divers archives et SRPM

Première étape, le fichier RPM source du JDK de Sun. Afin de montrer que c'est pas un fichier source comme les autres, il est appelé java-1.4.2-sun-1.4.2.nosrc.rpm. Il faut prendre le fichier source du paquet java-1.4.2-sun-1.4.2.

En général, le SRPM contient les sources du paquet, mais le noeud du problème est justement que seul Sun peut les distribuer. Il faut donc les récupérer à part, sur le site de sun ( obtenu du champ Url du paquet ). Au moment de la rédaction de cette article, le chemin est :

Choisir "Download" , prendre "J2SE v 1.4.2_05 SDK ", accepter le formulaire après l'avoir lu, et enfin, choisir "Linux Platform", "self-extracting file" ( surtout pas "RPM in self-extracting file" mais bien "self-extracting file" ).

Le fichier téléchargé de 30 Mo doit être déposé dans ~/rpm/SOURCES/.

La dernière étape, c'est de d'installer le paquet jpackage-utils. Soit vous récupérez le paquet à la main, soit vous passez par urpmi. Pour l'installation à la main, le paquet est sur le site de jpackage, ou sur les miroirs Mandrakelinux. Pour l'installer, urpmi /le_chemin_vers_le_RPM devrait suffire.

Pour l'installation via urpmi, easy urpmi doit avoir tout ce qu'il faut, il suffit d'ajouter 'jpackage', de la même façon que toutes les autres sources.

Recompilation du RPM

Si tout va bien, vous devez être en mesure de recompiler le RPM. Vérifier que le fichier j2sdk-1_4_2_04-linux-i586.bin est dans rpm/SOURCES, et lancez la recompilation, avec la commande :

$ rpm --rebuild java-1.4.2-sun-1.4.2.05-3jpp.nosrc.rpm

Le RPM va se charger d'accepter la licence que vous avez déjà acceptée au moment du téléchargement, et de répondre aux questions de l'installeur de Sun. À la fin, vous devriez voir ça :

Checking for unpackaged file(s): /usr/lib/rpm/check-files /tmp/java-1.4.2-sun-1.4.2.04-3jpp-buildroot
Wrote: /home/users/misc/rpm/SRPMS/java-1.4.2-sun-1.4.2.04-3jpp.nosrc.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-devel-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-src-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-demo-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-plugin-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-fonts-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-alsa-1.4.2.04-3jpp.i586.rpm
Wrote: /home/users/misc/rpm/RPMS/i586/java-1.4.2-sun-jdbc-1.4.2.04-3jpp.i586.rpm
Executing(%clean): /bin/sh -e /tmp/rpm-tmp.6627
+ umask 022
+ cd /home/users/misc/rpm/BUILD
+ cd j2sdk1.4.2_04
+ rm -rf /tmp/java-1.4.2-sun-1.4.2.04-3jpp-buildroot
+ exit 0

Vous avez donc , dans rpm/RPMS/i586/ les RPMs de la JVM. Pour pouvoir exécuter des logiciels en Java, il vous faut java-1.4.2-sun. Le RPM java-1.4.2-sun-devel contient le compilateur et ce qu'il faut pour commencer à développer en Java. Enfin, java-1.4.2-sun-plugin est un plugin Java pour mozilla et konqueror.

Ajout de jpackage, section nonfree, pour Mandrakelinux

Muni de vos RPMs, il va falloir les mettre quelque part pour les utiliser. Pour cela, le plus facile est d'ajouter une source local pour votre gestionnaire de paquet. Copiez les RPMs dans le dossier de votre choix, on va dire /var/local/urpmi/jpackage/. Puis, il faut génerer les indexes à l'aide du programme genhdlist du paquet rpmtools. Enfin, vous devez ajouter la source à urpmi.

# export DEST=/var/local/urpmi/jpackage/
# mkdir -p $DEST
# cp -R ~/rpm/RPMS/i586/java* $DEST
# ( cd $DEST; genhdlist )
# urpmi.addmedia jpackage_local file://$DEST with ./hdlist.cz

Et voila, maintenant, urpmi java-1.4.2 vous installera la JVM de Sun, et vous pouvez installer les RPMs de jpackage. Si vous préférez une autre JVM ou une autre version, vous pouvez procédez de la même manière. Les paquets sont normalement installables cote à cote en parallèle.

Haut


Fin du chapitre