10.6. Tratamiento de los argumentos en línea de órdenes

Python admite la creación de programas que se pueden ejecutar desde la línea de órdenes, junto con argumentos y opciones tanto de estilo corto como largo para especificar varias opciones. Nada de esto es específico al XML, pero este script hace buen uso del tratamiento de la línea de órdenes, así que parece buena idea mencionarlo.

Es complicado hablar del procesamiento de la línea de órdenes sin entender cómo es expuesta a un programa en Python, así que escribiremos un programa sencillo para verlo.

Ejemplo 10.20. Presentación de sys.argv

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.

#argecho.py
import sys

for arg in sys.argv: 1
    print arg
1 Cada argumento de la línea de órdenes que se pase al programa estará en sys.argv, que es una lista. Aquí estamos imprimiendo cada argumento en una línea aparte.

Ejemplo 10.21. El contenido de sys.argv

[usted@localhost py]$ python argecho.py             1
argecho.py
[usted@localhost py]$ python argecho.py abc def     2
argecho.py
abc
def
[usted@localhost py]$ python argecho.py --help      3
argecho.py
--help
[usted@localhost py]$ python argecho.py -m kant.xml 4
argecho.py
-m
kant.xml
1 La primera cosa que hay de saber sobre sys.argv es que contiene el nombre del script que se ha ejecutado. Aprovecharemos este conocimiento más adelante, en Capítulo 16, Programación Funcional. No se preocupe de eso por ahora.
2 Los argumentos en la línea de órdenes se separan con espacios, y cada uno aparece como un elemento separado en la lista sys.argv.
3 Las opciones como --help también aparecen como un elemento aparte en la lista sys.argv.
4 Para hacer las cosas incluso más interesantes, algunas opciones pueden tomar argumentos propios. Por ejemplo, aquí hay una opción (-m) que toma un argumento (kant.xml). Tanto la opción como su argumento son simples elementos secuenciales en la lista sys.argv. No se hace ningún intento de asociar uno con otro; todo lo que tenemos es una lista.

Así que como puede ver, ciertamente tenemos toda la información que se nos pasó en la línea de órdenes, pero sin embargo no parece que vaya a ser tan fácil hacer uso de ella. Para programas sencillos que sólo tomen un argumento y no tengan opciones, podemos usar simplemente sys.argv[1] para acceder a él. No hay que avergonzarse de esto; yo mismo lo hago a menudo. Para programas más complejos necesitará el módulo getopt.

Ejemplo 10.22. Presentación de 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 Antes de nada, mire al final del ejemplo y advierta que está llamando a la función main con sys.argv[1:]. Recuerde, sys.argv[0] es el nombre del script que está ejecutándose; no nos interesa eso a la hora de procesar la línea de órdenes, así que lo eliminamos y pasamos el resto de la lista.
2 Aquí es donde sucede todo el procesamiento interesante. La función getopt del módulo getopt toma tres parámetros: la lista de argumentos (que conseguimos de sys.argv[1:]), una cadena que contiene todas las posibles opciones de un solo carácter que acepta este programa, y una lista de opciones más largas que son equivalentes a las versiones de un solo carácter. Esto es bastante confuso a primera vista, y se explicará con más detalle enseguida.
3 Si algo va mal al intentar analizar estas opciones de la línea de órdenes getopt emitirá una excepción, que capturamos. Le dijimos a getopt todas las opciones que entedemos, así que probablemente esto significa que el usuario pasó alguna opción que no entendemos.
4 Como es práctica estándar en el mundo de UNIX, cuando se le pasan opciones que no entiende, el script muestra un resumen de su uso adecuado y sale de forma controlada. Fíjese en que no he enseñado aquí la función usage. Tendrá que programarla en algún lado y hacer que imprima el resumen apropiado; no es automático.

Así que, ¿qué son todos esos parámetros que le pasamos a la función getopt? Bien, el primero es simplemente la lista sin procesar de opciones y argumentos de la línea de órdenes (sin incluir el primer elemento, el nombre del script, que ya eliminamos antes de llamar a la función main). El segundo es la lista de opciones cortas que acepta el script.

"hg:d"

-h
print usage summary
-g ...
use specified grammar file or URL
-d
show debugging information while parsing

La primera opción y la tercera son autosuficientes; las especificamos o no y hacen cosas (imprimir ayuda) o cambian estados (activar la depuración). Sin embargo, a la segunda opción (-g) debe seguirle un argumento, que es el nombre del fichero de gramática del que hay que leer. De hecho puede ser un nombre de fichero o una dirección web, y aún no sabemos qué (lo averiguaremos luego), pero sabemos que debe ser algo. Se lo decimos a getopt poniendo dos puntos tras g en el segundo parámetro de la función getopt.

Para complicar más las cosas, el script acepta tanto opciones cortas (igual que -h) como opciones largas (igual que --help), y queremos que hagan lo mismo. Para esto es el tercer parámetro de getopt, para especificar una lista de opciones largas que corresponden a las cortas que especificamos en el segundo parámetro.

["help", "grammar="]

--help
print usage summary
--grammar ...
use specified grammar file or URL

Aquí hay tres cosas de interés:

  1. Todas las opciones largas van precedidas de dos guiones en la línea de órdenes, pero no incluimos esos guiones al llamar a getopt. Se sobreentienden.
  2. La opción --grammar siempre debe ir seguida por un argumento adicional, igual que la -g. Esto se indica con un signo igual, "grammar=".
  3. La lista de opciones largas es más corta que la de las cortas, porque la opción -d no tiene una versión larga correspondiente. Esto es correcto; sólo -d activará la depuración. Pero el orden de las opciones cortas y largas debe ser el mismo, así que necesitará espeficar todas las opciones cortas que tienen su versión larga correspondiente primero, y luego el resto de opciones cortas.

¿Sigue confundido? Miremos el código real y veamos si tiene sentido en ese contexto.

Ejemplo 10.23. Tratamiento de los argumentos de la línea de órdenes en 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 contendrá el nombre del fichero de gramática que estemos usando. La inicializamos aquí en cado de que no se especifique en la línea de órdenes (usando la opción -g o --grammar).
2 La variable opts que obtenemos de getopt contiene una lista de tuplas opción y argumento. Si la opción no toma argumentos, entonces arg será None. Esto hace más sencillo iterar sobre las opciones.
3 getopt valida que las opciones de la línea de órdenes sean aceptables, pero no hace ningún tipo de conversión entre cortas y largas. Si especificamos la opción -h, opt contendrá "-h"; si especificamos --help, opt contendrá "--help". Así que necesitamos comprobar ambas.
4 Recuerde que la opción -d no tiene la opción larga correspondiente, así que sólo nos hace falta comprobar la corta. Si la encontramos, damos valor a una variable global a la que nos referiremos luego para imprimir información de depuración. (Usé esto durante el desarrollo del script. ¿Qué, pensaba que todos estos ejemplos funcionaron a la primera?)
5 Si encuentra un fichero de gramática, con la opción -g o con --grammar, guardamos el argumento que le sigue (almacenado en arg) dentro de la variable grammar sustituyendo el valor por omisión que inicializamos al principio de la función main.
6 Esto es todo. Hemos iterado y tenido en cuenta todas las opciones de línea de órdenes. Eso significa que si queda algo deben ser argumentos. Ese resto lo devuelve la función getopt en la variable args. En este caso, estamos tratándolo como material fuente para el analizador. Si no se especifican argumentos en la línea de órdenes args será una lista vacía, y source una cadena vacía.