programmez-en-d/main.whata

409 lines
17 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "L'environnement du programme"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Gouget"
]
Nous avons vu que [c main()] est une fonction. L'exécution du programme commence par [c main()] et continue avec d'autres fonctions depuis là. La définition de [c main()] que nous avons utilisée jusqu'à maintenant est~ :
[code=d <<<
void main()
>>>]
Selon cette définition, [c main()] ne prend pas de paramètre et ne retourne pas de valeur. En réalité, il est impossible de ne pas retourner de valeur, parce que les environnements qui démarrent les programmes s'attendent à avoir des valeurs de retour de leur part. Même s'il est possible de spécifier le type de retour de [c main()] comme [c void], elle retourne quand même une valeur.
[ = La valeur de retour de [c main()]
Les programmes sont toujours démarrés par une entité dans un environnement particulier. L'entité qui démarre le programme peut être l'interpréteur de commandes (''shell'') où l'utilisateur tape le nom du programme et appuie sur la touche Entrée, ou ça peut être un environnement de développement où le programmeur clique sur le bouton ''Exécuter'', etc.
Le programme communique son code de retour à son environnement par la valeur de retour de [c main()].
Une valeur de retour de 0 veut dire que tout s'est bien passé et n'importe quelle autre valeur indique un certain type d'échec. Bien que le choix de la valeur de retour appartienne au programmeur, la valeur 0 veut toujours dire réussite, par convention.
[p Note~ : | Seules les valeurs dans l'intervalle ~[0,127~] sont portables~ ; tous les environnements ne prennent pas en charge d'autres valeurs.]
Les valeurs autres que 0 peuvent avoir différents sens en fonction de chaque programme. Par exemple, le programme Unix [c ls], qui est utilisé pour lister le contenu des répertoires, retourne 1 pour les erreurs mineurs et 2 pour les erreurs sérieuses. Dans beaucoup d'environnements, la valeur de retour du programme qui a été exécuté le plus récemment dans la console peut être vu à travers la variable d'environnement [c $?]. Par exemple, quand on demande à [c ls] de lister un fichier qui n'existe pas, sa valeur de retour non nulle peut être vue avec [c $?] comme ci-dessous.
[p Note~ : | dans la session en ligne de commande suivante, les lignes qui commencent par [c #] indiquent les lignes que l'utilisateur tape. Si vous voulez essayer les mêmes commandes, vous devez saisir le contenu de ces lignes sans le caractère [c #]. En outre, les commandes tapées plus loin démarrent un programme nommé [c essai]~ ; remplacer ce nom avec le nom de votre programme test.]
De plus, même si les exemples suivant affichent des interactions dans une console Linux, elles seraient similaires mais pas exactement les mêmes dans des consoles d'autres systèmes d'exploitation.
[code <<<
# ls un_fichier_qui_nexiste_pas
ls: impossible d'accéder à un_fichier_qui_nexiste_pas: Aucun fichier ou dossier de ce type
# echo $?
2 ← La valeur de retour de ls
>>>]
[ = [c main] retourne toujours une valeur
Certains programmes que nous avons écrits jusqu'à maintenant on levé des exceptions quand ils ne pouvaient pas continuer leurs tâches. D'après ce que nous avons vu jusqu'à maintenant, quand une exception est levée, le programme finit avec un message d'erreur [c object.Exception].
Quand cela arrive, même si [c main()] a été définie comme retournant [c void], une valeur de 1 est automatiquement retournée à l'environnement du programme.
Voyons ceci en action avec ce programme qui se finit avec une exception~ :
[code=d <<<
void main()
{
throw new Exception("Il y a eu une erreur.");
}
>>>]
Même si le type de retour est spécifié comme [c void], la valeur de retour est 1~ :
[code <<<
# ./essai
object.Exception: Il y a eu une erreur.
# echo $?
1
>>>]
De façon similaire, les fonction [c void main()] qui terminent correctement retournent aussi 0 automatiquement. Voyons ceci avec le programme suivant qui termine correctement~ :
[code=d <<<
import std.stdio;
void main()
{
writeln("Fait !");
}
>>>]
Le programme retourne 0~ :
[code <<<
# ./essai
Fait !
# echo $?
0
>>>]
]
[ = Spécifier la valeur de retour
Retourner une valeur de retour spécifique depuis [c main()] est la même chose que retourner une valeur depuis n'importe quelle fonction. Le type de retour doit être positionné à [c int] et la valeur doit être retournée par l'instruction [c return]~ :
[code=d <<<
import std.stdio;
int main()
{
int nombre;
write("Veuillez saisir un nombre entre 3 et 6 : ");
readf(" %s", &nombre);
if ((nombre < 3) || (nombre > 6)) {
stderr.writefln("ERREUR : %s n'est pas valide !", nombre);
return 111;
}
writefln("Merci d'avoir saisi %s.", nombre);
return 0;
}
>>>]
Quand le nombre saisi est dans l'intervalle de validité, la valeur de retour du programme est 0~ :
[code <<<
# ./essai
Veuillez saisir un nombre entre 3 et 6 : 5
Merci d'avoir saisi 5.
# echo $?
0
>>>]
Quand le nombre est hors de l'intervalle de validité, la valeur de retour du programme est 111~ :
[code <<<
# ./essai
Veuillez saisir un nombre entre 3 et 6 : 10
ERREUR : 10 n'est pas valide !
# echo $?
111
>>>]
La valeur de 111 dans le programme précédent est arbitraire~ ; normalement 1 est utilisé pour le code d'échec.
]
]
[ = Le flux standard d'erreur [c stderr]
Le programme ci-avant utilise le flux [c stderr]. Il est utilisé pour écrire des messages d'erreurs. Ce flux est un des trois flux standards~ :
- [c stdin]~ : flux d'entrée standard~ ;
- [c stdout]~ : flux de sortie standard~ ;
- [c stderr]~ : flux d'erreur standard.
Quand un programme est lancé dans une console, normalement les messages qui sont écrits dans [c stdout] et [c stderr] apparaissent tous dans la console. Quand c'est nécessaire, il est possible de rediriger ces sorties individuellement. Ceci est hors du programme de ce chapitre et les détails peuvent varier pour chaque ''shell''.
]
[ = Paramètres de [c main()]
Il est courant que les programmes prennent des paramètres depuis l'environnement qui les a démarrés. Par exemple, nous venons de passer un nom de fichier en option en ligne de commande à [c ls]. Il y a deux options en ligne de commande dans la ligne suivante~ :
[code <<<
# ls -l essai
-rwxr-xr-x 1 acehreli users 460668 Nov 6 20:38 essai
>>>]
L'ensemble des paramètres en ligne de commande et leurs significations sont entièrement définis par le programme. Chaque programme documente son usage, dont la signification de chaque paramètre.
Les arguments qui sont utilisés lors du démarrage d'un programme D sont passés à la fonction [c main()] de ce programme sous la forme d'une tranche de [c string]s. Il suffit de définir [c main()] comme prenant un paramètre de type [c string~[~]] pour avoir accès aux arguments du programme. Le nom de ce paramètre est souvent appelé [c args]. Le programme suivant affiche tous les arguments qui ont été passés au programme~ :
[code=d <<<
import std.stdio;
void main(string[] args)
{
foreach (i, arg; args) {
writefln("Argument %-3s: %s", i, arg);
}
}
>>>]
Lançons ce programme avec des arguments arbitraires~ :
[code <<<
# ./essai des arguments en ligne de commande 42 --une-option
Argument 0 : ./essai
Argument 1 : des
Argument 2 : arguments
Argument 3 : en
Argument 4 : ligne
Argument 5 : de
Argument 6 : commande
Argument 7 : 42
Argument 8 : --une-option
>>>]
Le premier argument est toujours le nom du programme, tel que l'utilisateur l'a écrit. Les autres arguments apparaissent dans l'ordre dans lequel ils ont été saisis.
La manière d'utiliser les arguments est complètement du ressort du programme. Le programme suivant affiche ses deux arguments dans l'ordre inverse~ :
[code=d <<<
import std.stdio;
int main(string[] args)
{
if (args.length != 3) {
stderr.writefln("ERREUR ! Utilisation correcte :\n"
" %s mot1 mot2", args[0]);
return 1;
}
writeln(args[2], ' ', args[1]);
return 0;
}
>>>]
Le programme affiche aussi son utilisation correcte s'il n'y a pas exactement deux mots~ :
[code <<<
# ./essai
ERREUR ! Utilisation correcte :
./essai mot1 mot2
# ./essai mot salut
salut mot
>>>]
]
[ = Options en ligne de commande et le module [c std.getopt]
C'est tout ce qu'il y a à savoir sur les paramètres et la valeur de retour de [c main()]. Cependant, analyser les arguments est une tâche répétitive. Le module [c std.getopt] est conçu pour aider à analyser les options en lignes de commande du programme.
Certains paramètres comme «~ mot~ » et «~ salut~ » dans l'exemple précédent sont purement des données utilisées par le programme. D'autres types de paramètres sont appelés [* options en ligne de commande], et sont là pour changer le comportement du programme. Un exemple d'une option en ligne de commande qu'on a déjà rencontré est [c -l], qui a été passé à [c ls].
Les options en ligne de commande rendent les programmes plus utiles en supprimant le besoin qu'un utilisateur humain interagisse avec le programme pour obtenir de lui un comportement particulier. Avec les options en ligne de commande, les programmes peuvent être lancés à partir de scripts et leurs comportements peuvent être spéficiés à travers les options en ligne de commande.
Même si la syntaxe et le sens des arguments en ligne de commande de chaque programme est spécifique à ce programme, leur format est plus ou moins standard. Par exemple, en POSIX, les options en ligne de commande commencent par «~ [c <<<-->>>]~ » suivi par le nom de l'option, et les valeurs viennent après des caractères «~ [c =]~ »~ :
[code <<<
# ./essai --an-option=17
>>>]
Le module [c std.getopt] simplifie l'analyse de telles options. Il offre plus de possibilités que celles qui vont être couvertes dans cette section.
Concevons un programme qui affiche des nombres aléatoires. Prenons le minimum, le maximum et le nombre total de ces nombres en arguments du programme. On s'attendra à la syntaxe suivante pour obtenir ces valeurs depuis la ligne de commande~ :
[code <<<
# ./essai --nombre=7 --minimum=10 --maximum=1
>>>]
La fonction [c getopt()] analyse et affecte ces valeurs aux variables. Comme quand on utilise [c readf()], on doit spécifier les adresses des variables au moyen de l'opérateur [c &]~ :
[code=d <<<
import std.stdio;
import std.getopt;
import std.random;
void main(string[] args)
{
int nombre;
int minimum;
int maximum;
getopt(args,
"nombre", &nombre,
"minimum", &minimum,
"maximum", &maximum);
foreach (i; 0 .. nombre) {
write(uniform(minimum, maximum + 1), ' ');
}
writeln();
}
>>>]
[code <<<
# ./essai --nombre=7 --minimum=10 --maximum=15
11 11 13 11 14 15 10
>>>]
La plupart des options en lignes de commande de la plupart des programmes ont aussi des syntaxes plus courte. Par exemple, [c -c] peut avoir le même sens que [c --nombre]. Une syntaxe alternative pour chaque option peut être spécifié après un caractère «~ [c >|]~ ». Il peut y avoir plus d'un raccourci par option~ :
[code=d <<<
getopt(args,
"nombre|c", &nombre,
"minimum|n", &minimum,
"maximum|x", &maximum);
>>>]
Il est commun d'utiliser un seul tiret pour la version courte et le caractère «~ [c =]~ » est généralement omis.
[code <<<
# ./essai -c7 -n10 -x15
11 13 10 15 14 15 14
>>>]
[c getopt()] convertit les arguments depuis le type [c string] vers le type de chaque variable. Par exemple, comme la variable [c nombre] ci-dessus est un [c int], [c getopt()] convertit la valeur spécifiée avec l'argument [c -~-nombre] en [c int]. Lorsque cela est nécessaire, de telles conversions peuvent aussi être faites explicitement avec [c to].
Jusqu'à maintenant, nous avons utilisé [c std.conv.to] seulement vers [c string]. [c to] peut en fait convertir depuis n'importe quel type vers n'importe quel type pourvu que cette conversion soit possible. Par exemple, le programme suivant utilise [c to] pour convertir ses arguments vers [c size_t]~ :
[code=d <<<
import std.stdio;
import std.conv;
void main(string[] args)
{
// Le nombre par défaut est 10
size_t nombre = 10;
if (args.length > 1) {
// Il y a un argument
nombre = to!size_t(args[1]);
}
foreach (i; 0 .. nombre) {
write(i * 2, ' ');
}
writeln();
}
>>>]
Le programme produit 10 nombres quand il n'y a pas d'argument spécifié~ :
[code <<<
# ./essai
0 2 4 6 8 10 12 14 16 18
# ./essai 3
0 2 4
>>>]
]
[ = Les variables d'environnement
L'environnement dans lequel le programme est démarré fournit des variables que le programme peut utiliser. On peut accéder aux variables d'environnement avec [c std.process.getenv]. Le programme suivant affiche la variable d'environnement [c PATH].
[code=d <<<
import std.stdio;
import std.process;
void main()
{
writeln(getenv("PATH"));
}
>>>]
La sortie~ :
[code <<<
# ./essai
/usr/local/bin:/usr/bin:/home/acehreli/dmd/linux/bin
>>>]
[c std.process.environment] donne accès aux variables d'environnement à travers la syntaxe des tableaux associatifs~ :
[code=d <<<
import std.process;
// ...
writeln(environment["PATH"]);
>>>]
Cependant, [c environment] n'est en réalité pas un tableau associatif. Lorsque c'est nécessaire, [c environment] peut être converti vers un tableau associatif avec [c toAA()]~ :
[code=d <<<
string[string] envVars = environment.toAA();
>>>]
]
[ = Lancer d'autres programmes
Les programmes peuvent lancer d'autres programmes et devenir l'environnement de ces programmes. La fonction [c executeShell] de module [c std.process] permet cela.
[c executeShell] exécute son paramètre comme s'il avait été saisi dans le terminal. Elle retourne aussi bien le code de retour que la sortie de cette commande dans un n-uplet. Les n-uplets (''tuples'') sont des structures qui ressemblent aux tableaux~ ; nous les verrons plus tard dans le chapitre sur les n-upplets.
[code=d <<<
import std.stdio;
import std.process;
void main()
{
const resultat = executeShell("ls -l deneme");
const codeRetour = resultat[0];
const sortie = resultat[1];
writefln("ls a retourné %s.", codeRetour);
writefln("Sa sortie:\n%s", sortie);
}
>>>]
Le résultat~ :
[output <<<
# ./deneme
ls a retourné 0.
Sa sortie:
-rwxrwxr-x. 1 acehreli acehreli 1359178 Apr 21 15:01 deneme
>>>]
]
[ = Résumé
- Même quand elle est définie avec un type de retour [c void], [c main()] retourne automatiquement 0 en cas de réussite et 1 en cas d'échec.
- [c stderr] est approprié pour afficher des messages d'erreurs.
- [c main()] peut prendre des paramètres [c string~[~]].
- [c std.getopt] facilite l'analyse des options en ligne de commande.
- [c std.process] facilite l'accès aux variables d'environnement et le lancement d'autres programmes.
]
[ = Exercices
# [
Écrivez une calculatrice qui prend un opérateur et deux opérandes en arguments de la ligne de commande. Le programme doit prendre en charge l'utilisation suivante~ :
[code <<<
# ./essai 3.4 x 7.8
26.52
>>>]
[p Note~ : | parce que le caractère «~ [c *]~ » a une signification spéciale sur la plupart des ''shells'', j'ai utilisé «~ [c x]~ ». Vous pouvez toujours utiliser «~ [c *]~ » tant qu'il est échappé ainsi~ : «~ [c \*]~ » (NdT~ : ou ainsi~ : «~ [c "*"]~ »).]
]
# Écrivez un programme qui demande à l'utilisateur quel programme lancer, démarre ce programme et retourne sa valeur de retour.
[[part:corrections/main | … Les solutions]]
]