Licence CC BY-SA

Arguments de la ligne de commande

Ligne de commande

Pour l’instant nous appelons nos programmes depuis la ligne de commande en tapant python program.py (mais nous savons aussi comment utiliser ./program.py sous Linux).

Dans les deux cas, cela fait appel à l’interpréteur Python en lui donnant le chemin de notre programme en argument. Mais il est possible de renseigner d’autres arguments lors du lancement et ceux-ci seront transmis à notre programme.

Ils seront accessibles sous la forme d’une liste de chaînes de caractères, la liste argv qu’il faudra importer depuis le module sys (un module qui gère les informations sur le système).

import sys

print(sys.argv)
program.py

À l’utilisation, nous recevons bien les différents arguments passés au programme.

% python program.py
['program.py']
% python program.py foo bar
['program.py', 'foo', 'bar']
% python program.py 1 2 3
['program.py', '1', '2', '3']

On voit que le premier argument est toujours le nom du programme.

Comme indiqué, il ne s’agit que de chaînes de caractères et il va donc falloir convertir les types lorsque cela est nécessaire. Par exemple avec cette mini-calculatrice.

import sys

a = int(sys.argv[1])
b = int(sys.argv[2])
print(a + b)
addition.py
% python addition.py 3 5
8
% python addition.py 10 -3
7

Mais attention, notre code plantera méchamment si nous ne fournissons pas suffisamment d’arguments.

% python addition.py 1
Traceback (most recent call last):
  File "addition.py", line 4, in <module>
    b = int(sys.argv[2])
IndexError: list index out of range

En effet, sys.argv est une liste ordinaire, et si sa taille n’est que de 2, alors elle ne possède pas d’élément à l’index 2.

Pour nous prémunir de ce genre d’erreurs, il faut donc vérifier la taille de la liste avant d’accéder à ses éléments. Et généralement dans ces cas là, on quittera le programme en affichant un message expliquant comment l’appeler.

Pour quitter un programme à tout moment, on peut faire appel à la fonction sys.exit.

import sys

if len(sys.argv) < 3:
    print('Usage: addition.py nb1 nb2')
    sys.exit()

a = int(sys.argv[1])
b = int(sys.argv[2])
print(a + b)

À l’utilisation c’est tout de suite plus propre.

% python addition.py 1 2
3
% python addition.py 1
Usage: addition.py nb1 nb2

Pour plus de généricité, on pourrait écrire print(f'Usage: {sys.argv[0]} nb1 nb2') évitant d’inscrire en dur le nom du programme. Le premier élément de sys.argv sera toujours présent, notre programme n’aurait pas pu être appelé sinon.

Sortie standard et sortie d’erreur

Il y a un seul soucis avec notre message d’erreur : celui-ci est imprimé sur la sortie standard.

Qu’est-ce que la sortie standard ?

Sur Linux, les programmes qui tournent sont automatiquement reliés à 3 périphériques :

  • L’entrée standard, celle qui récupère le texte entré sur le terminal, accessible via input().
  • La sortie standard, où est affiché par défaut tout ce qui sort du programme sur le terminal (avec print par exemple).
  • La sortie d’erreur, spécifiquement dédiée aux erreurs.

Les sorties standard et d’erreur sont toutes deux affichées par défaut sur le terminal, mais elles sont pourtant différentes. Dans un shell Bash, il est possible d’utiliser l’opérateur > pour rediriger le flux de sortie standard vers un fichier, et l’opérateur 2> pour la sortie d’erreur.

% python addition.py > out 2> err

Si l’on inspecte nos fichiers, on constate bien que out contient le message d’erreur et que err est vide. On aimerait que ce soit l’inverse en cas d’erreur.

En fait, chaque sortie correspond à un fichier ouvert par défaut par le programme. Pour la sortie standard, il s’agit de sys.stdout. (standard output). On peut l’utiliser comme tout autre fichier ouvert en écriture.

>>> import sys
>>> sys.stdout.write('hello\n')
hello
6
>>> print('world', file=sys.stdout)
world

Le 6 qui apparaît n’est que le retour de l’appel à write (6 caractères ont été écrits).

Et de façon similaire, on a sys.stderr qui correspond à la sortie d’erreur.

>>> print('error', file=sys.stderr)
error

Bien sûr la différence n’est pas flagrante dans cet exemple, elle le sera si l’on redirige les sorties standard et d’erreur vers des fichiers différents. Ce qui est généralement fait pour la journalisation d’un programme.

import sys

print('standard output', file=sys.stdout)
print('error output', file=sys.stderr)
outputs.py
% python outputs.py > out 2> err
% cat out
standard output
% cat err
error output

Ainsi, nous pouvons remplacer notre code de traitement d’erreur par le suivant.

if len(sys.argv) < 3:
    print(f'Usage: {sys.argv[0]} nb1 nb2', file=sys.stderr)
    sys.exit()

Parseur d'arguments

Manipuler sys.argv ça va quand on a des arguments simples comme deux nombres ici, mais ça devient vite compliqué pour gérer les options passées à un programme.

En effet, il serait difficile de gérer manuellement les arguments d’un appel tel que python cmd.py -v -f pdf --foo=bar --foo2 42 photo.jpg. C’est pourquoi des outils existent pour analyser à votre place les arguments, les valider en fonction de ce qui est attendu, et les classer convenablement.
Le module argparse de la bibliothèque standard propose l’un de ces outils.

argparse fournit un type ArgumentParser que l’on peut instancier pour obtenir un parseur d’arguments.

import argparse

parser = argparse.ArgumentParser()
cmd.py

Des méthodes sont ensuite disponibles sur ce parseur pour le personnaliser et préciser les arguments que l’on attend.
Notamment la méthode add_argument qui permet de demander à gérer un nouvel argument.

Celle-ci accepte de nombreuses options que je ne détaillerai pas ici. Sachez simplement qu’elle attend en premier le nom voulu pour l’agument.

  • Si ce nom est de type -x alors elle gèrera l’argument comme -x VALEUR lors de l’appel au programme.
  • S’il est de type --abc, elle gèrera --abc=VALEUR et --abc VALEUR.
  • S’il ne débute pas par un tiret, alors il s’agira d’un argument positionnel du programme.

Un paramètre action sert à préciser quoi faire de l’argument rencontré.

  • store_const permet de simplement stocker la valeur associée à l’argument.
  • store_true permet de stocker True si l’argument est présent et False sinon.

Une valeur par défaut pour l’argument peut être renseignée avec le paramètre default.

Par défaut, la valeur de l’argument sera stockée dans l’objet résultant sous le nom de l’argument. Il est cependant possible de choisir un autre nom pour le stockage à l’aide du paramètre dest.

Enfin, il est possible d’utiliser le paramètre type pour convertir automatiquement la valeur d’un argument vers le type voulu.

Voilà par exemple comment nous pourrions traiter les arguments présentés pour la commande plus haut.

parser.add_argument('-v', dest='verbose', action='store_true')
parser.add_argument('-f', dest='format', default='text')
parser.add_argument('--foo')
parser.add_argument('--foo2', type=int)
parser.add_argument('file')

args = parser.parse_args()
print(args)
print('Verbose:', args.verbose)
print('Format:', args.format)
cmd.py
% python cmd.py -v -f pdf --foo=bar --foo2 42 photo.jpg
Namespace(verbose=True, format='pdf', foo='bar', foo2=42, file='photo.jpg')
Verbose: True
Format: pdf
% python cmd.py doc.odt
Namespace(verbose=False, format='text', foo=None, foo2=None, file='doc.odt')
Verbose: False
Format: text

Pour plus d’informations au sujet du module argparse, vous pouvez consulter sa page de documentation : https://docs.python.org/fr/3/library/argparse.html.