IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Cours complet pour apprendre à programmer en D


précédentsommairesuivant

41. L'environnement du programme

Nous avons vu que main() est une fonction. L'exécution du programme commence par main() et continue avec d'autres fonctions depuis là. La définition de main() que nous avons utilisée jusqu'à maintenant est :

 
Sélectionnez
void main()

Selon cette définition, 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 main() comme void, elle retourne quand même une valeur.

41-1. La valeur de retour de 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 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.

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 ls, qui est utilisé pour lister le contenu des répertoires, retourne 1 pour les erreurs mineures 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 $?. Par exemple, quand on demande à ls de lister un fichier qui n'existe pas, sa valeur de retour non nulle peut être vue avec $? comme ci-dessous.

Dans la session en ligne de commande suivante, les lignes qui commencent par # 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 #. En outre, les commandes tapées plus loin démarrent un programme nommé essai ; remplacez ce nom avec le nom de votre programme test.

De plus, même si les exemples suivants 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.

 
Sélectionnez
# ls un_fichier_qui_nexiste_pas
ls: impossible `'accéder à un_fichier_qui_n'existe_pas: Aucun fichier ou dossier de ce type
# echo $?
2          ← La valeur de retour de ls

41-1-1. main retourne toujours une valeur

Certains programmes que nous avons écrits jusqu'à maintenant ont levé des exceptions quand ils ne pouvaient pas continuer leur tâche. D'après ce que nous avons vu jusqu'à maintenant, quand une exception est levée, le programme finit avec un message d'erreur object.Exception.

Quand cela arrive, même si main() a été définie comme retournant void, une valeur de 1 est automatiquement retournée à l'environnement du programme.

Voyons ceci en action avec ce programme qui se termine avec une exception :

 
Sélectionnez
void main()
{
    throw new Exception("Il y a eu une erreur.");
}

Même si le type de retour est spécifié comme void, la valeur de retour est 1 :

 
Sélectionnez
# ./essai
object.Exception: Il y a eu une erreur.
# echo $?
1

De façon similaire, les fonctions void main() qui terminent correctement retournent aussi 0 automatiquement. Voyons ceci avec le programme suivant qui termine correctement :

 
Sélectionnez
import std.stdio;
 
void main()
{
    writeln("Fait !");
}

Le programme retourne 0 :

 
Sélectionnez
# ./essai
Fait !
# echo $?
0

41-1-2. Spécifier la valeur de retour

Retourner une valeur de retour spécifique depuis main() est la même chose que retourner une valeur depuis n'importe quelle fonction. Le type de retour doit être positionné à int et la valeur doit être retournée par l'instruction return :

 
Sélectionnez
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 :

 
Sélectionnez
# ./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 :

 
Sélectionnez
# ./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.

41-2. Le flux standard d'erreur stderr

Le programme ci-avant utilise le flux stderr. Il est utilisé pour écrire des messages d'erreur. Ce flux est un des trois flux standards :

  • stdin : flux d'entrée standard ;
  • stdout : flux de sortie standard ;
  • stderr : flux d'erreur standard.

Quand un programme est lancé dans une console, normalement les messages qui sont écrits dans stdout et 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.

41-3. Paramètres de 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 à ls. Il y a deux options en ligne de commande dans la ligne suivante :

 
Sélectionnez
# 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 main() de ce programme sous la forme d'une tranche de strings. Il suffit de définir main() comme prenant un paramètre de type string[] pour avoir accès aux arguments du programme. Le nom de ce paramètre est souvent appelé args. Le programme suivant affiche tous les arguments qui ont été passés au programme :

 
Sélectionnez
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 :

 
Sélectionnez
# ./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 :

 
Sélectionnez
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 :

 
Sélectionnez
# ./essai
ERREUR ! Utilisation correcte :
  ./essai mot1 mot2
# ./essai mot salut
salut mot

41-4. Options en ligne de commande et le module std.getopt

C'est tout ce qu'il y a à savoir sur les paramètres et la valeur de retour de main(). Cependant, analyser les arguments est une tâche répétitive. Le module 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 -l, qui a été passé à 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écifié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 sont spécifiques à ce programme, leur format est plus ou moins standard. Par exemple, en POSIX, les options en ligne de commande commencent par « --» suivi par le nom de l'option, et les valeurs viennent après des caractères « = » :

 
Sélectionnez
# ./essai --an-option=17

Le module 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 :

 
Sélectionnez
# ./essai --nombre=7 --minimum=10 --maximum=1

La fonction getopt() analyse et affecte ces valeurs aux variables. Comme quand on utilise readf(), on doit spécifier les adresses des variables au moyen de l'opérateur & :

 
Sélectionnez
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();
}

Sortie :

 
Sélectionnez
# ./essai --nombre=7 --minimum=10 --maximum=15
11 11 13 11 14 15 10

La plupart des options en ligne de commande de la plupart des programmes ont aussi des syntaxes plus courtes. Par exemple, -c peut avoir le même sens que --nombre. Une syntaxe alternative pour chaque option peut être spécifiée après un caractère « | ». Il peut y avoir plus d'un raccourci par option :

 
Sélectionnez
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 « = » est généralement omis.

 
Sélectionnez
# ./essai -c7 -n10 -x15
11 13 10 15 14 15 14

getopt() convertit les arguments depuis le type string vers le type de chaque variable. Par exemple, comme la variable nombre ci-dessus est un int, getopt() convertit la valeur spécifiée avec l'argument --nombre en int. Lorsque cela est nécessaire, de telles conversions peuvent aussi être faites explicitement avec to.

Jusqu'à maintenant, nous avons utilisé std.conv.to seulement vers string. 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 to pour convertir ses arguments vers size_t :

 
Sélectionnez
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é :

 
Sélectionnez
# ./essai
0 2 4 6 8 10 12 14 16 18
# ./essai 3
0 2 4

41-5. 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 std.process.getenv. Le programme suivant affiche la variable d'environnement PATH.

 
Sélectionnez
import std.stdio;
import std.process;
 
void main()
{
    writeln(getenv("PATH"));
}

La sortie :

 
Sélectionnez
# ./essai
/usr/local/bin:/usr/bin:/home/acehreli/dmd/linux/bin

std.process.environment donne accès aux variables d'environnement à travers la syntaxe des tableaux associatifs :

 
Sélectionnez
import std.process;
// ...
    writeln(environment["PATH"]);

Cependant, environment n'est en réalité pas un tableau associatif. Lorsque c'est nécessaire, environment peut être converti vers un tableau associatif avec toAA() :

 
Sélectionnez
string[string] envVars = environment.toAA();

41-6. Lancer d'autres programmes

Les programmes peuvent lancer d'autres programmes et devenir l'environnement de ces programmes. La fonction executeShell de module std.process permet cela.

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.

 
Sélectionnez
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 :

 
Sélectionnez
# ./deneme
ls a retourné 0.
Sa sortie:
-rwxrwxr-x. 1 acehreli acehreli 1359178 Apr 21 15:01 deneme

41-7. Résumé

  • Même quand elle est définie avec un type de retour void, main() retourne automatiquement 0 en cas de réussite et 1 en cas d'échec.
  • stderr est approprié pour afficher des messages d'erreur.
  • main() peut prendre des paramètres string[].
  • std.getopt facilite l'analyse des options en ligne de commande.
  • std.process facilite l'accès aux variables d'environnement et le lancement d'autres programmes.

41-8. Exercices

  1. É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 :

     
    Sélectionnez
    # ./essai 3.4 x 7.8
    26.52

    Parce que le caractère « * » a une signification spéciale sur la plupart des shells, j'ai utilisé « x ». Vous pouvez toujours utiliser « * » tant qu'il est échappé ainsi : « \* » (NDT Ou ainsi : « "*" »).

  2. Écrivez un programme qui demande à l'utilisateur quel programme lancer, démarre ce programme et retourne sa valeur de retour.

Les solutionsEnvironnement du Programme - Correction.


précédentsommairesuivant