|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment écrire une commande AmigaDOS avec le système 2.0 ? Cet article tentera de donner une réponse en proposant un modèle de programme encore appelé "squelette". Ce modèle a été écrit avec le SAS C 5.10 mais pourra, moyennant quelques adaptations, être utilisé par d'autres environnements. Les commandes que nous écrirons utiliseront massivement les services offerts par la dos.library du 2.0. Ces nouvelles fonctions seront donc étudiées dans cet article. Nous examinerons dans un premier temps la division naturelle des programmes de commandes puis nous étudierons l'analyse de la ligne de commande, nous détaillerons ensuite les fonctions de la dos.library qui permettent d'effectuer des entrées-sorties tamponnées. Nous terminerons par le traitement des jokers ("wildcards"). Une commande Qu'est-ce que c'est, à quoi ça sert ? Une commande AmigaDOS est un programme, une application très simple qui a un rôle bien délimité et que l'on appelle à partir du CLI ou du Shell. Ce programme se passe de toute interface utilisateur à base d'icônes, de fenêtres, de menus, etc. C'est le cas de List, Copy, More, etc. On lui fournit des paramètres au moment de l'appel puis elle s'exécute en affichant éventuellement des messages d'erreur. Elle peut aussi poser elle-même des questions de manière interactive, mais seul le clavier permet de répondre. Les commandes Shell doivent cependant offrir une souplesse d'utilisation et une certaine convivialité (même s'il s'agit d'une interface textuelle et non graphique). Pourquoi utiliser des commandes alors que l'Amiga permet la programmation d'interfaces utilisateur conviviales ? Il y a plusieurs raisons : dès qu'il s'agit de manoeuvres un peu compliquées, si l'on par doit par exemple manipuler des chemins d'accès à des fichiers, si l'on peut opérer de plusieurs manières différentes, si l'on veut enchaîner plusieurs commandes de manière à réaliser un script, si l'on veut rediriger le résultat sur l'imprimante ou dans un fichier pour le conserver, alors il est beaucoup plus simple de considérer le programme comme une simple instruction qui peut être appelée directement ou mise dans un programme comme en BASIC. Lors de son exécution, une commande suit plusieurs étapes : elle doit premièrement récupérer les arguments qu'on lui a passés. Ces arguments se présentent sous la forme d'une ligne de texte appelée ligne de commande. La commande doit ensuite analyser cette ligne, séparer les options des arguments et isoler chaque argument. Par exemple, dans le cas de la ligne de commandes "copy toto to ram:", l'analyse de la ligne de commande permet de déterminer que "toto" est le nom du fichier sources et que "ram:" est la destination. L'étape suivante est le traitement que réalise la commande. Ce traitement dépend bien sûr de la commande mais on peut effectuer quelques observations : ces traitements correspondent à des algorithmes souvent simples et utilisent généralement les entrées-sorties de façon intensive. En particulier, les manipulations de textes sont courantes. Enfin, la commande libère les ressources (mémoire, fichiers ouverts, etc.) qu'elle a utilisées et communique au Shell le succès ou l'échec du traitement grâce à un code d'erreurs. Nous verrons qu'AmigaDOS propose un double système de codes d'erreurs. Détaillons maintenant chacune des étapes. Récupérer la ligne de commande La dos.library du 2.0 offre l'appel GetStrArg() qui permet de récupérer un pointeur sur la ligne de commande. C'est la solution la plus propre mais je dois signaler l'existence de problèmes avec le débogueur cpr de Lattice. Dans le cas du langage C, la ligne de commande est fournie sous une forme transformée par les variables "argc" (nombre d'arguments) et "argv" (tableau contenant les arguments). Un code de démarrage (le fameux "c.o" dans le cas de Lattice) a déjà séparé les divers arguments qui composaient la ligne de commande (il s'agit d'un début d'analyse). Cette séparation peut paraître avantageuse mais nous verrons que la dos.library du système 2.0 permet de récupérer et d'analyser automatiquement la ligne de commande. On peut utiliser une méthode plus subtile : l'environnement du compilateur SAS permet de commencer un programme C avant le traitement de la ligne de commande : il est en effet inutile d'effectuer deux fois cette analyse. Le point d'entrée s'appelle "_main(char *line)". L'argument de ce point d'entrée est précisément la ligne de commande à laquelle a été rajouté le nom du programme. Cependant, en utilisant "_main()" on perd également l'accès aux fonctions d'entrée-sortie standard. Nous verrons plus loin qu'on en utilise d'autres très proches. Pour les utilisateurs d'autres environnements, la fonction "main()" peut quand même être utilisée, ainsi que le programme principal en Pascal. Analyser la ligne de commande Une fois la ligne de commande récupérée, il faut l'analyser. On ne peut pas se contenter de passer des paramètres en vrac et de laisser l'application se débrouiller avec. De plus, il faut un minimum de conventions pour empêcher les ambiguïtés ("copy A B" marche dans quel sens ?), et surtout il faut garantir une certaine homogénéité de la syntaxe. On peut ainsi deviner à l'avance à peu près quoi fournir à la commande sans devoir consulter le mode d'emploi (rappelez-vous Copy et DiskCopy en MS-DOS qui ne marchent pas dans le même sens !). Reprenons l'exemple de la commande Copy. Cette commande admet des arguments de la forme :
Ceci mérite commentaire. Tout d'abord, nous nous apercevons que les commandes AmigaDOS utilisent des mots-clés pour différencier leurs arguments. Dans l'exemple, le mot-clé "from" sert à identifier les noms des fichiers sources et le mot-clé "to" la destination. De même, diverses options sont activées à l'aide de tels mots-clés. Les crochets indiquent que ces mots-clés sont optionnels. Ceci peut se comprendre pour les options mais comment reconnaître la source et la destination sans "from" et "to" ? Par défaut, le chemin de destination est le dernier nom et tous les autres fichiers sont considérés comme des fichiers sources. Comment peut-on savoir quelle est la syntaxe d'une ligne de commande donnée ? Si la commande est correctement programmée et suit les directives de Commodore, il suffit de taper :
Dans le cas de Copy, on obtient : D'après le manuel du CLI...
A cet argument rationnel, je ne peux opposer qu'une préférence personnelle. Il me semble que la syntaxe AmigaDOS est plus proche du langage naturel et elle me parait plus facile à utiliser. Il faut noter qu'AmigaDOS permet la programmation d'un syntaxe pseudo-Unix. En effet, on peut indiquer pour chaque mot-clé un équivalent. Pour convertir Copy en commande de type Unix, on peut proposer la template string suivante :
Cependant, on ne peut pas comme sous Unix grouper les options derrière un tiret comme :
Comment procède-t-on pratiquement à l'analyse de la ligne de commande ? Les possibilités sont tellement nombreuses qu'il serait fastidieux de procéder soi-même. La dos.library du 2.0 offre un appel système qui effectue cette analyse : ReadArgs(). Le premier argument de cet appel n'est autre que la template string qui indique à l'appel comment il doit comprendre la ligne. Cette chaîne de caractères joue le même rôle que la chaîne de formatage dans un appel scanf(). Le second argument est un tableau de pointeur ou seront rangés les différents pointeurs sur les arguments toujours dans l'ordre indiqué dans la template string. Ce tableau comprend autant d'éléments que la template string contient de mots-clés. Si un argument est absent, le pointeur correspondant est nul. Les objets pointés diffèrent selon le type d'argument :
L'appel retourne un pointeur sur une structure RDArgs. Il est important de conserver ce pointeur car en cas d'analyse réussie, le système alloue de la mémoire pour y placer les résultats. Cette mémoire devra être libérée à la fin de la commande par un appel FreeArgs(). Si l'analyse échoue, le résultat de ReadArgs() est nul. Exemple : si la template string est "DE/A,VERS/A,DELAI/N", l'analyse de la ligne de commande suivante "VERS Italie DE France DELAI 5" conduira au tableau suivant :
Les erreurs Au niveau du DOS, les erreurs sont définies par un code qui indique le type d'erreur et un niveau qui indique sa gravité. La seule nouveauté du système 2.0 de ce point de vue est d'introduire une sortie standard d'erreur à l'instar du "stderr" d'Unix. Ceci permet de rediriger sélectivement les messages d'erreurs et de les séparer des sorties normales de la commande. La plupart des appels de la dos.library ne retournent pas de code d'erreurs mais le placent quelque part dans le système. Le programmeur peut alors récupérer ce code en appelant IoErr() afin de traiter lui-même ces erreurs. Dans le cas des commandes, le traitement se limite souvent à fermer les ressources et à afficher un message. La dos.library offre une fonction qui associe au code un message standardisé et l'affiche dans la sortie d'erreur. Ce message peut être précédé d'une chaîne de caractères indiquée par le programmeur. En fait, cette fonction ressemble beaucoup à l'appel Unix "perror()". On peut supposer que les messages qu'affichent cet appel seront automatiquement traduits quand sortira le système d'exploitation en français. La gravité de l'erreur est indiquée par un nombre retourné à la fin de la commande (ce nombre doit être placé dans le registre d0, ce qui est obtenu en C en le donnant en argument de la "fonction" exit()). Par convention, 0 correspond à une exécution sans erreur, 5 à un avertissement (warning) et 20 à une erreur fatale. Ce niveau d'erreur peut être utilisé par certaines commandes (voir la description de la commande "if") et s'il est plus élevé que le niveau défini par la commande "failat", il interrompt le script en cours. Nous pouvons maintenant continuer notre squelette. Notre squelette est terminé. Pour l'utiliser il faut le remplir en complétant la template string et la fonction FinCommande(). Ensuite, il faut écrire à la suite le corps du programme lui-même dans la fonction Main. Comme nous l'avons signalé dans l'introduction, l'auteur d'une commande aura souvent à manier les entrées-sorties de façon intensive. Le système 2.0 apporte toute la gestion des entrées-sorties dédiées au texte. Habituellement reléguée dans des bibliothèques C, elles sont maintenant directement accessibles grâce à la dos.library. Voici les nouvelles fonctions qui concernent les entrées-sorties texte : Un autre problème fréquemment rencontré lors d'écriture de commandes est le traitement des jockers. AmigaDOS définit en effet tout une panoplie de possibilités pour spécifier plusieurs fichiers. Ainsi `(#?.info) signifie tous les fichiers sauf ceux qui se terminent par ".info". Le problème de savoir si un nom de fichier correspond à un motif ("pattern" en anglais) donné requiert beaucoup de travail. Heureusement, la dos.library apporte trois appels qui permettent de chercher dans un répertoire tous les fichiers dont le nom correspond à un motif : MatchFirst(), MatchNext() et MatchEnd(). Voici comment l'on doit utiliser ces appels. Il faut disposer d'une structure AnchorPath que nous appellerons "APath". Des précisions sur l'emploi des trois appels et la description de cette structure sont disponibles dans dos/dosasl.h. Certains champs de cette structure permettent d'influer sur la recherche mais pour une utilisation simple, il est inutile de détailler cette structure. Il existe un second appel qui permet de stocker dans un tampon l'ensemble des FileBlockInfo des fichiers correspondant à un motif donné : il s'agit d'ExAll(). Je ne le détaillerai pas mais le lecteur intéressé peut consulter les ROM Kernel Manuals. Conclusion Toutes ces améliorations apportées par le système 2.0 rendent l'écriture des commandes très aisée. Avec le système 1.3, il aurait fallu tout programmer soi-même : l'analyse de la ligne de commande, le traitement des jockers et l'affichage des messages d'erreur. Il faut savoir que ces progrès ont été fortement inspirés par l'arp.library dont Commodore a intégré la plupart des fonctions dans la dos.library. Les programmeurs qui utilisent ARP ne seront donc pas du tout dépaysés.
|