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.
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...
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.
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:
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
|
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 ?
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.
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.
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]
Il y en a de nombreuses :
print printf ...
gensub substr tolower index split ...
fflush close nextline system
rand sin exp log ...
systime strftime
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.
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 -fExplications :
maVariable
.nbLignes
, affiche la ligne préfixée par son numéro. Le numéro est formaté pour occupper quattre caractères.
Les lignes suivantes (à partir du if
) montrent l'équivalence entre le bloc du dessus et un test if dans le bloc principal. La variable nbCommentaires2 est incrémentée pour chaque ligne commençant par un dièse ou vide.maVariable
est toujours définie et que son contenu n'a pas été altéré. Il indique également le nombre total de lignes traitées et le nombre de lignes de commentaires ou vides présentes dans le fichier.Exécution
# chmod a+x ceScript.awkawk permet, bien sûr l'utilisation de :
if (condition) {instructions} else {instructions}
: si alors sinonwhile (condition) {instructions}
: boucle tantquedo {instructions} while ()
: boucle répéter jusqu'àfor (expr1; expr2; expr3) {instructions}
: boucle pour for (var in tableau)
: boucle pour toutes les valeur du tableau. Il existe aussi while (var ni tableau),
if (var in tableau) ...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.
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).
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 :
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.
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 diversLes 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.
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
par Fred, correction et ajouts de Marc
Comment écrire de petits scripts permettant d'automatiser la réalisation de taches répétitives.
#!/bin/shNote : "#!" se prononce "she bang", soit "chi-bang".
#!/bin/shComment on l'éxécute ? C'est simple il suffit de faire :
echo "Bonjour, Monde !"
echo "un premier script est né."
[user@becane user]$ sh bonjour_mondeC'est pas cool, vous préféreriez taper quelque chose comme :
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
[user@becane user]$ ./bonjour_mondeC'est possible si vous avez au préalable rendu votre script exécutable par la commande :
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
[user@becane user]$ chmod +x bonjour_mondeRésumons : un script shell commence par : #!/bin/sh, il contient des commandes du shell et est rendu exécutable par chmod +x.
[user@becane user]$ ./bonjour_monde
Bonjour, Monde !
un premier script est né.
[user@becane user]$ _
#!/bin/shQue fait le script ? Les commentaires ne l'expliquent pas ! Ce sont de mauvais commentaire. Par contre :
# pour i parcourant tous les fichiers,
for i in * ; do
# copier le fichier vers .bak
cp $i $i.bak
# fin pour
done
#!/bin/shLà, au moins, on sait ce qu'il se passe. (Il n'est pas encore important de connaître les commandes de ces deux fichiers.)
# on veut faire un copie de tous les fichiers
for i in * ; do
# sous le nom *.bak
cp $i $i.bak
done
#!/bin/shQue fait-il ? Il affiche, les uns après les autres les trois premiers paramètres du script, donc si l'on tappe :
echo le paramètre \$1 est \"$1\"
echo le paramètre \$2 est \"$2\"
echo le paramètre \$3 est \"$3\"
$ ./essai01 paramètre unDonc, 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.
le paramètre $1 est "paramètre"
le paramètre $2 est "un"
le paramètre $3 est ""
$ _
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 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 :
le paramètre $1 est "Mail"
le paramètre $2 est "essai01"
le paramètre $3 est "nsmail"
$ _
$ ./essai01 \*Hé oui, on a "échappé" le caractère * donc il a perdu sa signification particulière : il est redevenu un simple *.
le paramètre $1 est "*"
le paramètre $2 est ""
le paramètre $3 est ""
$ _
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/shSi vous tapez :
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\"
$ ./essai02 1 2 3 4 5 6 7 8 9 10 11 12 13A 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).
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"
$ _
1:#!/bin/sh(Les numéros ne sont là que pour repérer les lignes, il ne faut pas les taper)
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
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.
#!/bin/shCe script se déroule ainsi :
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."
./essai02bisLors du déroulement du script vous devez valider vos entrées en appuyant sur la touche "Entrée".
Entrez votre prénom : Marc
Entrez votre nom de login : spoutnik
Le nom de login de Marc est spoutnik.
#!/bin/shAffichera :
echo 2+3*5 = $((2+3*5))
MACHIN=12
echo MACHIN*4 = $[$MACHIN*4]
$ sh essai03Vous 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.
2+3*5 = 17
MACHIN*4 = 48
if <test> ; thenIl faut savoir que tous les programmes renvoient une valeur. Cette valeur est stockée dans la variable ? dont la valeur est, rappelons le : "$?".
<instruction 1>
<instruction 2>
...
<instruction n>
else
<instruction n+1>
...
<instruction n+p>
fi
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/shAffichera :
if true ; then
echo Le premier test est VRAI($?)
else
echo Le premier test est FAUX($?)
fiif false ; then
echo Le second test est VRAI($?)
else
echo Le second test est FAUX($?)
fi
$ ./testOn 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.
Le premier test est VRAI(0)
Le second test est FAUX(1)
$ _
Bon, évidemment, des tests de cet ordre ne paraissent pas très utiles. Voyons maintenant de vrais tests.
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 /etcCette 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 ; thenCette 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" ;-).
echo L\'utilisateur frederic existe.
else
echo L'utilisateur frederic n\'existe pas.
fi
#!/bin/shSeules 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.
# 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
for <variable> in <liste de valeurs pour la variable> ; doVoyons comment ça fonctionne. Supposons que nous souhaitions renommer tous nos fichiers *.tar.gz en *.tar.gz.old, nous taperons le script suivant :
<instruction 1>
...
<instruction n>
done
#!/bin/shSimple, 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 :
# 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
1:#!/bin/shExplications : 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 ').
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
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).
while <un test> ; doSupposons, par exemple que vous souhaitiez afficher les 100 premiers nombres (pour une obscure raison), alors vous taperez :
<instruction 1>
...
<instruction n>
done
i=0Remarque : -lt signifie "lesser than" ou "plus petit que" (et -gt signifie "plus grand", ou "greater than").
while [ $i -lt 100 ] ; do
echo $i
i=$[$i+1]
done
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").
#!/bin/shAu 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.
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
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.
À vous de jouer !
par Marc
T'as d'belles boîtes t'sais ?
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.
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
).
#!/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 # # #
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.
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.
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.
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)
[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.
Bill ? Porte ? Je vous laisse méditer là dessus et passe à l'étape suivante.
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). )
[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
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éeOui, 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.
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...
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.
#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/binDont 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
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
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)
par Teotihuacan
Ceci est une courte présentation de DCOP.
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.
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.
dcop
, vous obtiendrez la liste des applications qui ont ouvert une interface DCOP.dcop APPLICATION
, vous obtiendrez la liste des interfaces DCOP disponibles pour cette application. Les applications sont désignées par leur nom.dcop APPLICATION INTERFACE
, vous obtiendrez la liste des fonctions qui sont à votre disposition pour cette interface. Et pour chaque fonction, vous obtiendrez le type de résultat auquel vous devez vous attendre et les arguments nécessaires pour la fonction : c'est le prototype de la fonction. Il se présente sous la forme : TYPE_RETOURNÉ NOM_FONCTION(TYPE_DES_ARGUMENTS)
.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.
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 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 en action
Vous allez pouvoir utiliser les possibilités de DCOP à plusieurs niveaux :
Vous allez pouvoir inclure ce genre de communication dans vos propres applications KDE. Pour cela, je vous renvoie à La Documentation de l'API.
Cela vous permet d'étendre vous possibilités de scripts en pilotant les applications KDE. Par exemple, pour Amarok (lecteur multimédia de KDE) rien ne vous empêche de créer un script qui en fonction du pourcentage de la piste lue, revient soit au début de la chanson, soit à la chanson précédente. Ça peut être utilisé quand on configure les touches multimédia de son clavier.
Kommander : c'est une application qui permet à partir d'une interface de lier des commandes shell. Kommander est une application qui repose beaucoup sur DCOP, car la communication entre les différents composants de l'interface est assurée par DCOP. Pour plus de détails, je vous renvoie à la page de Kommander sur KDE-Apps.org.
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 ;-).
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.
man dlopen man dlsym
Si vous voulez que votre librairie soit réutilisable et efficace, vous devez respecter quelques notions fondamentales de la programmation.
Pas Bien ! Cette solution fait appel à une variable globale pour connaitre la taille du tableau. C'est déconseillé ! /* * ... */ int tailleTab; long * traiteTab (long * tab) { /* * * Traitement des données * */ } /* * ... */
Bien ! Ici, la taille du tableau est passée en paramètre à la fonction. C'est mieux ! /* * ... */ long * traiteTab (long * tab, int tailleTab) { /* * * Traitement des données * */ } /* * ... */
Si le fichier à été inclus, _MON_TEST_H est défini et il ne le sera pas à nouveau. #ifndef _MON_TEST_H #define _MON_TEST_H 1 /* * ... *ici, le contenu normal du fichier monTest.h * ... */ #endif
#include
: pour inclure un fichier dans un autre en c,
on utilise la directive #include
.
#include "mesH/monTest.h"
#include <orb/orbit.h>
gcc -I. etc...
Pour devenir incollables sur ces amusantes petites choses, apprenez par coeur les man pages de gcc et ld. ;-)
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 !
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 !
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 ?
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...
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.
man gcc
man ar
man ld
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] = 2063084132En 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 ...
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.
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.
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.
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.
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.
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
.
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 ;-).
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.
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 ...
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.
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 :
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
<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 :
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)
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.
tar xvfy freetype-2.0.3.tar.bz2 cd freetype-2.0.3 make setup make su make installCela installe freetype sous /usr/local. Vous pouvez ensuite redevenir "simple utilisateur".
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.1Editez 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/binPuis tapez:
su make installIl ne s'agit pas d'une erreur de ma part, il faut bien taper directement make install, c'est comme ça !
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
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.
Image normale Sans anti-aliasing
(-jaune)Avec dessin
du brectAvec brect
Sans anti-aliasing
/* * 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
/* * 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
Notez :
par Jice et Jonesy
Installation et introduction à Perl.
Note de Jice : Perl est un langage très polyvalent. LE jeu, j'ai nommé Frozen Bubble, est même programmé en Perl !
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.
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
Remarque : les chemins de recherche des librairies sont définit
dans le tableau @INC
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.
Pour déclarer plusieurs variables et un tableau en une seule
commande, faire :
my ($var1, $var2, @tab);
! | : 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é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. |
?: | : 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. |
< <= > >= | : plus petit, plus petit ou égal, plus grand, plus grand ou égal |
== != | : égal, différent |
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. |
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). |
@_ 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
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.
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).
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}
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.
while (<STDIN>) {Autre exemple, une itération de 1 à 9
next if /^#/;
...
}
$cpt = 1;ou bien :
while ($cpt < 10) {
...
$cpt++;
}
$cpt = 1;Pour plus d'information sur ces constructions : man perlsyn.
while ($cpt < 10) {
...
}
continue {
$cpt++;
}
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).
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;
où $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.
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.
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".
Les captures $1, $2... peuvent être utilisées lors d'une
substitution :
$s =~ /win(?:dows)?\s(..)/winblows
$1/
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.
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;
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
!)
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 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 ";
}
}
}
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.
à 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.
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 ...
}
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 !? ;-)
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...
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.
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.
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
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.
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.
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.
Fin du chapitre