10.6. Manipuler les arguments de la ligne de commande

Python supporte complètement la création de programmes qui peuvent être lancés en ligne de commande, à l'aide d'arguments et de drapeaux longs ou cours pour spécifier diverses options. Cela n'est nullement spécifique à XML, mais comme ce script fait grand usage du traitement en ligne de commande, il est très à propos d'y faire ici mention.

Il est difficile de parler du traitement en ligne de commande sans aborder la façon dont les arguments sont passés au programme Python, commencez donc par un petit programme en guise d'introduction.

Exemple 10.20. Introduction à sys.argv

Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.

#argecho.py
import sys

for arg in sys.argv: 1
    print arg
1 Chaque argument de ligne de commande passé au programme est ajouté à sys.argv, qui est un objet liste. Ici le script affiche chaque argument sur une ligne séparée.

Exemple 10.21. Les caractéristiques de sys.argv

[you@localhost py]$ python argecho.py             1
argecho.py
[you@localhost py]$ python argecho.py abc def     2
argecho.py
abc
def
[you@localhost py]$ python argecho.py --help      3
argecho.py
--help
[you@localhost py]$ python argecho.py -m kant.xml 4
argecho.py
-m
kant.xml
1 Ce qu'il faut d'abord retenir de l'objet sys.argv est qu'il contient le nom du script que vous appelez. Vous en tirerez avantage plus tard, dans le Chapitre 16, Programmation fonctionnelle. Ne vous en souciez pas pour le moment.
2 Les arguments de la ligne de commande sont séparés par des espaces et chacun se présente comme un élément distinct dans la liste sys.argv.
3 Les drapeaux de la ligne de commande, comme --help, se présentent également comme des éléments propres dans la liste sys.argv.
4 Pour corser le tout, certains drapeaux de la ligne de commande prennent eux-mêmes des arguments. Par exemple, vous avez ici un drapeau (-m) qui prend un argument (kant.xml). Aussi bien le drapeau que son argument sont présentés comme des éléments distincts dans la liste sys.argv. Rien n'est fait pour les associer; vous n'obtenez rien de plus qu'une liste.

Comme vous pouvez le voir maintenant, vous disposez indiscutablement de toutes les informations passées à la ligne de commande, mais, de nouveau, il apparaît que tout ne sera pas forcément facile à utiliser. Pour des programme simples qui ne nécessite qu'un seul argument et pas de drapeau, vous pouvez simplement utiliser sys.argv[1] pour accéder à l'argument. Aucune honte à avoir; je fais ça tout le temps. Pour des programmes plus complexes, il vous faut recourir au module getopt.

Exemple 10.22. Introduction à getopt


def main(argv):                         
    grammar = "kant.xml"                 1
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) 2
    except getopt.GetoptError:           3
        usage()                          4
        sys.exit(2)                     

...

if __name__ == "__main__":
    main(sys.argv[1:])
1 Tout d'abord, regardez au bas de l'exemple et remarquez que vous appelez la fonction main avec sys.argv[1:]. Rappelez-vous, sys.argv[0] est le nom du script en cours; vous n'avez pas à vous en soucier pour le traitement en ligne de commande, aussi le supprimez-vous et envoyez-vous le reste de la liste.
2 C'est ici que se trouve la partie intéressante du traitement. La fonction getopt du module getopt prend trois paramètres : la liste des arguments (que vous obtenez à partir de sys.argv[1:]), une chaîne contenant tous les drapeaux courts possibles acceptés par le programme et une liste des drapeaux plus longs qui correspondent aux versions courtes. C'est bien confus à première vue, mais l'explication détaillée vient plus bas.
3 Si un dysfonctionnement survient au moment d'analyser les drapeaux de ligne de commande, getopt déclenche une exception, que vous récupérez ensuite. Comme vous avez indiqué à getopt tous les drapeaux que vous connaissiez, il y a fort à parier que l'utilisateur final a passé des drapeaux de ligne de commande qui vous sont inconnus.
4 Comme il est de coutume dans le monde UNIX, quand le script reçoit des drapeaux qu'il ne connaît pas, vous mettez fin au programme de la manière la plus élégante qui soit, en fournissant un résumé des règles de bon usage. Remarquez que je n'ai pas présenté ici la fonction usage. Vous aurez encore besoin d'ajouter dans un coin quelques lignes de code pour afficher le résumé approprié; ce n'est pas automatique.

Quels sont donc tous ces paramètres que vous passez à la fonction getopt ? Et bien, le premier est simplement la liste brute des arguments et des drapeaux de ligne de commande (à l'exception du premier élément, le nom du script, que vous avez éliminé avant d'appeler la fonction main). Le deuxième est la liste des drapeaux courts acceptés par le script.

"hg:d"

-h
affiche les règles d'usage
-g ...
utilise le fichier de grammaire ou l'URL spécifié
-d
montre l'information de débogage au cours du traitement

Le premier et le troisième drapeaux fonctionnent de manière autonome; vous les spécifiez ou non et le cas échéant ils exécutent une action (affichage l'aide) ou changent un état (actionnement du débogage). Au contraire, le deuxième drapeau (-g) doit être suivi par un argument, qui est le nom du fichier de grammaire à analyser. En fait, ce peut être un nom de fichier ou une adresse web, et vous ne savez pas encore lequel (vous l'apprendrez plus tard); mais vous êtes certains qu'il doit bien y avoir quelque chose. Aussi le signalez-vous à getopt en ajoutant deux points après g, le second paramètre de la fonction getopt.

Pour compliquer davantage les choses, le script accepte soit des drapeaux courts (comme -h) soit des drapeaux longs (comme --help) et vous voulez que ces derniers effectuent la même chose. D'où l'utilité du troisième paramètre de getopt : spécifier une liste de drapeaux longs qui correspondent aux drapeaux courts du second paramètre.

["help", "grammar="]

--help
affiche les règles d'usage
--grammar ...
utilise le fichier de grammaire ou l'URL spécifié

Trois choses à remarquer ici :

  1. Tous les drapeaux longs sont précédés par deux tirets sur la ligne de commande, mais vous n'avez pas besoin d'inclure ces tirets quand vous appelez getopt. Ils sont implicites.
  2. Le drapeau --grammar doit toujours être suivi par un argument additionnel, comme pour le drapeau -g. C'est indiqué par un signe égal, "grammar=".
  3. La liste des drapeaux longs est plus courte que la liste des drapeaux courts, parce que le drapeau -d n'a pas de version longue correspondante. Très bien; seul -d activera le débogage. Mais l'ordre des drapeaux courts et des drapeaux longs a besoin d'être le même, aussi vous devez d'abord spécifier tous les drapeaux courts qui possèdent un drapeau long correspondant, puis tout le reste des drapeaux courts.

Encore confus ? Tournez-vous vers le code en question et voyez si, dans ce contexte, cela fait sens.

Exemple 10.23. Manipuler les arguments de la ligne de commande dans kgp.py


def main(argv):                          1
    grammar = "kant.xml"                
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
    except getopt.GetoptError:          
        usage()                         
        sys.exit(2)                     
    for opt, arg in opts:                2
        if opt in ("-h", "--help"):      3
            usage()                     
            sys.exit()                  
        elif opt == '-d':                4
            global _debug               
            _debug = 1                  
        elif opt in ("-g", "--grammar"): 5
            grammar = arg               

    source = "".join(args)               6

    k = KantGenerator(grammar, source)
    print k.output()
1 La variable grammar gardera une trace du fichier de grammaire utilisé. Vous l'initialisez ici au cas où elle ne serait pas spécifiée à la ligne de commande (en utilisant les drapeaux -g ou --grammar).
2 La variable opts que vous récupérez à partir de getopt contient une liste de tuples : flag et argument. Si le drapeau ne prend pas d'argument, alors arg vaudra simplement None. Cela facilite le parcours des drapeaux.
3 La fonction getopt valide les drapeaux de ligne de commande qui sont acceptables, mais elle ne fait aucune sorte de conversion entre les drapeaux courts et les drapeaux longs. Si vous spécifiez le drapeau -h, opt contiendra "-h"; si vous spécifiez le drapeau --help, opt contiendra "--help". Aussi avez-vous besoin de tester les deux.
4 Rappelez-vous, le drapeau -d n'avait pas de drapeau long correspondant; aussi n'avez-vous besoin que de tester la forme courte. Si vous le détectez, vous déclarez une variable globale à laquelle vous vous référerez plus tard pour afficher les informations de débogage. (Je l'ai utilisé au cours du développement de ce script. Vous ne pensiez tout de même pas que ces exemples ont fonctionné du premier coup ?)
5 Si vous trouvez un fichier de grammaire, indiqué par les drapeaux -g ou --grammar, vous conservez l'argument qui le suit (stocké dans arg) dans la variable grammar, écrasant alors la valeur par défaut que vous aviez initialisée au début de la fonction main.
6 C'est tout. Vous avez parcouru et traité les drapeaux de la ligne de commande. Ce qui signifie qu'il ne peut rester alors que des arguments de ligne de commande. Ils sont retournés par la fonction getopt et placés dans la variable args. Dans ce cas, vous les traitez comme une source de données pour l'analyseur. Si aucun argument de ligne de commande n'est spécifié, args sera une liste vide et source contiendra au final une chaîne vide.