Introducción a la programación en Python
clase 9

Argumentos de línea de comando

Es habitual que los programas puedan manejar argumentos de línea de comando. Existe además una cierta convención respecto a cómo debe comportarse un programa de línea de comando, como se muestra a continuación mediante el programa ls.

In [1]:
ls ./tmp
archivo_dos_lineas.txt      otra_secuencia.csv  programa6.py
archivo_notas               programa1.py        programa7.py
archivo_nuevo.txt           programa2.py        programa8.py
archivo_tres_lineas.txt     programa3.py        programa9.py
archivo_una_sola_linea.txt  programa4.py        secuencia.csv
nuevo_como_diccionario.csv  programa5.py        secuencia_dictionary.csv

In [2]:
ls -l ./tmp
total 72
-rw-r--r-- 1 martin martin  67 sep 26 21:36 archivo_dos_lineas.txt
-rw-r--r-- 1 martin martin 146 sep 30 22:00 archivo_notas
-rw-r--r-- 1 martin martin  31 sep 30 22:00 archivo_nuevo.txt
-rw-r--r-- 1 martin martin  95 sep 28 10:32 archivo_tres_lineas.txt
-rw-r--r-- 1 martin martin  24 sep 26 19:56 archivo_una_sola_linea.txt
-rw-r--r-- 1 martin martin  90 sep 30 22:01 nuevo_como_diccionario.csv
-rw-r--r-- 1 martin martin  38 sep 30 22:00 otra_secuencia.csv
-rw-r--r-- 1 martin martin  72 sep 30 15:26 programa1.py
-rw-r--r-- 1 martin martin 168 sep 30 15:44 programa2.py
-rw-r--r-- 1 martin martin 198 sep 30 15:51 programa3.py
-rw-r--r-- 1 martin martin 200 sep 30 16:23 programa4.py
-rw-r--r-- 1 martin martin 236 sep 30 16:30 programa5.py
-rw-r--r-- 1 martin martin 242 sep 30 18:44 programa6.py
-rw-r--r-- 1 martin martin 563 sep 30 18:57 programa7.py
-rw-r--r-- 1 martin martin 582 sep 30 19:09 programa8.py
-rw-r--r-- 1 martin martin 562 sep 30 19:08 programa9.py
-rw-r--r-- 1 martin martin 126 sep 28 19:49 secuencia.csv
-rw-r--r-- 1 martin martin  82 sep 28 20:16 secuencia_dictionary.csv

In [3]:
ls --help
Modo de empleo: ls [OPCIÓN]... [FICHERO]...
Muestra información acerca de los FICHEROs (del directorio actual por defecto).
Ordena las entradas alfabéticamente si no se especifica ninguna de las
opciones -cftuvSUX ni --sort.

Los argumentos obligatorios para las opciones largas son también obligatorios
para las opciones cortas.
  -a, --all                  no oculta las entradas que comienzan con .
  -A, --almost-all           no muestra las entradas . y .. implícitas
      --author               con -l, imprime el autor de cada fichero
  -b, --escape               imprime escapes en estilo C para los caracteres no
                             gráficos
      --block-size=TAMAÑO    escalar tamaños por TAMAÑO antes de imprimirlos.
                               P.e., «--block-size=M» imprime los tamaños en
                               unidades de 1,048,576 bytes. Vea el formato de
                               TAMAÑO más abajo.
  -B, --ignore-backups       no listar las entradas implícitas que terminen en ~
  -c                         con -lt: ordenar por, y mostrar, ctime (hora de la
                               última modificación de la información de estado
                               del fichero)
                               con -l: mostrar ctime y ordenar por nombre
                               otro caso: ordenar por ctime, los más recientes
                               primero
  -C                         muestra las entradas por columnas
      --color[=CUÁNDO]       colorea la salida. Por omisión CUÁNDO es `always'
                               y puede ser también `never' o `auto'.
                               Más información abajo.
  -d, --directory            muestra las entradas de los directorios en lugar
                               de sus contenidos, y no sigue los enlaces
                               simbólicos
  -D, --dired                genera el resultado para el modo `dired' de Emacs
  -f                         no ordena, utiliza -aU, no utiliza -ls --color
  -F, --classify             añade un indicador (uno de */=@|) a las entradas
      --file-type            similar, pero no añade `*'
      --format=PALABRA       across -x, commas -m, horizontal -x, long -l,
                               single-column -1, verbose -l, vertical -C
      --full-time            como -l --time-style=full-iso
  -g                         como -l, pero no lista el propietario
      --group-directories-first
                             agrupa directorios antes que los ficheros
                               compatible con una opción --sort, pero cualquier
                               uso de --sort=none (-U) desactiva la agrupación
  -G, --no-group             en un listado largo, no muestra nombres de grupo
  -h, --human-readable       con -l, imprime los tamaños en formato legible
                               (p. ej. 1K 234M 2G)
  -H, --si                   análogo, pero utiliza potencias de 1000 y no de 1024
  -H, --dereference-command-line
                             sigue los enlaces simbólicos en la línea de
                             órdenes
      --dereference-command-line-symlink-to-dir
                             sigue cada enlace simbólico en la línea de
                             órdenes que apunte a un directorio
      --hide=PATRÓN          no lista las entradas implícitas que coinciden
                               con el patrón de shell PATRÓN
                               (las opciones -a o -A tienen prioridad)
      --indicator-style=PALABRA  añade un indicador con estilo PALABRA a los
                                 nombres de las entradas: none (predeterminado),
                                 slash (-p), file-type (--file-type), classify (-F)
  -i, --inode                muestra el número de índice de cada fichero
  -I, --ignore=PATRÓN        no lista las entradas que coincidan (encajen)
                               con PATRÓN de shell
  -k                         como --block-size=1K
  -l                         utiliza un formato de listado largo
  -L, --dereference          al mostrar la información de un fichero para un
                               enlace simbólico, muestra la información del
                               fichero al que apunta el enlace en lugar de la
                               del propio enlace
  -m                         rellena el ancho con una lista de entradas
                             separadas por comas
  -n, --numeric-uid-gid      como -l, pero muestra los IDs de usuario y grupo
                               numéricos
  -N, --literal              muestra los nombres literalmente (no trata p. ej.
                             los caracteres de control de forma especial)
  -o                         como -l, pero no muestra el grupo
  -p  --indicator-style=slash añade el indicador / a los directorios
  -q, --hide-control-chars   imprime ? en lugar de los caracteres no gráficos
      --show-control-chars   muestra los caracteres no gráficos tal y como
                             son (predeterminado a menos que el programa sea
                             `ls' y la salida sea un terminal)
  -Q, --quote-name           encierra los nombres de las entradas entre
                             comillas
      --quoting-style=PALABRA  utiliza el estilo de cita PALABRA para los
                               nombres de las entradas:
                               literal, locale, shell, shell-always, c, escape
  -r, --reverse              invierte el orden, en su caso
  -R, --recursive            muestra los subdirectorios recursivamente
  -s, --size                 muestra el tamaño de cada fichero, en bloques
  -S                         ordena los ficheros por tamaño
      --sort=PALABRA         ordena por PALABRA en vez de por nombre: none -U,
                             extension -X, size -S, time -t, version -v
      --time=PALABRA         con -l, muestra la fecha según PALABRA, en lugar
                               de la fecha de modificación:
                               atime -u, access -u, use -u, ctime -c, ó status -c;
                               utiliza la fecha especificada como clave de
                               ordenación si --sort=time
      --time-style=ESTILO    con -l, muestra la fecha utilizando el estilo ESTILO:
                               full-iso, long-iso, iso, locale, +FORMATO
                             FORMATO se interpreta como en `date'; si FORMATO
                             es FORMATO1<nuevalínea>FORMATO2, FORMATO1 se
                             aplica a los ficheros no recientes y FORMATO2
                             a los ficheros recientes; si ESTILO está precedido
                             por `posix-', ESTILO surte efecto solamente fuera
                             del local POSIX
  -t                         ordena por la fecha de modificación, el más
                               reciente primero
  -T, --tabsize=COLS         establece los topes de tabulación a cada COLS
                             en lugar de 8
  -u                         con -lt: ordena por atime y muestra atime (fecha
                               de último acceso al fichero)
                               con -l: muestra atime y ordena por nombre
                               en cualquier otro caso: ordena por atime
  -U                         no ordena; muestra las entradas en el orden del
                             directorio
  -v                         orden natural de números (de versión) dentro
                               del texto
  -w, --width=COLS           establece el ancho de la pantalla en lugar del
                             valor actual
  -x                         muestra las entradas por líneas en vez de por
                             columnas
  -X                         ordena alfabéticamente por la extensión de la
                             entrada
  -Z, --context              muestra el contexto de seguridad SELinux de cada
                               fichero
  -1                         muestra un fichero por cada línea
      --help     muestra esta ayuda y finaliza
      --version  informa de la versión y finaliza

TAMAÑO puede ser (o puede ser un entero seguido opcionalmente por) uno
de los siguientes:

KB 1000, K 1024, MB 1000*1000, M 1024*1024, y así sucesivamente para G, T, P,
E, Z, Y.

El uso de color para distinguir los tipos de ficheros está desactivado
por omisión y cuando se usa --color=never. Con --color=auto, ls produce
códigos de color solamente cuando la salida estándar está conectada a una
terminal. La variable de entorno LS_COLORS puede cambiar las opciones.
Utilice la orden dircolors para establecerla.

Estado de salida:
 0  si todo fue bien
 1  si hubo problemas menores (p. ej., no poder acceder a un subdirectorio),
 2  si hubo un serio problema (p. ej., no se puede acceder al argumento de la
                                 línea de órdenes)

Comunicar errores en ls a bug-coreutils@gnu.org
Página inicial de GNU coreutils: <http://www.gnu.org/software/coreutils/>
Ayuda general sobre el uso de software de GNU: <http://www.gnu.org/gethelp/>
Informe de errores de traducción ls a <http://translationproject.org/team/>
Para la documentación completa, ejecute: info coreutils `ls invocation'

Del ejemplo anterior se pueden derivar las siguientes observaciones:

  • El comando ls funciona incluso sin argumentos y despliega por defecto el contenido del directorio actual.
  • Si se quiere que liste otro directorio podemos especificarlo con un argumento posicional (el ./tmp en este caso).
  • El comportamiento del programa puede cambiarse usando un argumento opcional, como en el caso de ls -l
  • Es posible obtener un texto de ayuda sobre su funcionamiento con el argumento --help. Esto es muy útil cuando no sabemos qué hace el programa y como manejarlo (e.g. qué argumentos acepta).

El módulo argparse

Es el módulo recomendado en la biblioteca estándar de Python para analizar argumentos de línea de comando. A continuación algunos ejemplos de su funcionamiento.

El archivo programa1.py tiene el siguiente código:

(nota: las instrucciones %load no son parte del código, se usan para desplegarlo)

In [4]:
%load ./tmp/programa1.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.parse_args()

Si bien parece que el programa no hace nada al ejecutarse desde la línea de comando con diferentes argumentos se obtiene el comportamiento que se muestra a continuación.

In [5]:
!python3 ./tmp/programa1.py
In [6]:
!python3 ./tmp/programa1.py --help
usage: programa1.py [-h]

optional arguments:
  -h, --help  show this help message and exit

In [7]:
!python3 ./tmp/programa1.py --verbose
usage: programa1.py [-h]
programa1.py: error: unrecognized arguments: --verbose

In [8]:
!python3 ./tmp/programa1.py foo
usage: programa1.py [-h]
programa1.py: error: unrecognized arguments: foo

Si bien correr el programa sin argumentos de línea de comando no genera ninguna salida (no se gana mucho), se puede ver que se obtuvo un mensaje de ayuda sin mucho esfuerzo, con una única opción de línea de comando por defecto. Cabe señalar además que el manejo de argumentos de línea de comando no especificados se hace correctamente.

Argumentos posicionales

Veamos con el siguiente ejemplo como especificar argumentos posicionales.

In [9]:
%load ./tmp/programa2.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("echo", help="repite la cadena especificada aquí")
args = parser.parse_args()
print(args.echo)

Y si se lo ejecuta con diferentes argumentos de línea de comando se obtiene el siguiente comportamiento.

In [10]:
!python3 ./tmp/programa2.py -h
usage: programa2.py [-h] echo

positional arguments:
  echo        repite la cadena especificada aquí

optional arguments:
  -h, --help  show this help message and exit

In [11]:
!python3 ./tmp/programa2.py foo
foo

El siguiente ejemplo hace un uso algo más elaborado del argumento de entrada.

In [12]:
%load ./tmp/programa3.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("cuadrado", help="devuelve el cuadrado del número especificado", type=int)
args = parser.parse_args()
print(args.cuadrado**2)
In [13]:
!python3 ./tmp/programa3.py 4
16

Argumentos opcionales

Veamos ahora como especificar argumentos opcionales.

In [14]:
%load ./tmp/programa4.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
    print("verbosity turned on")
In [15]:
!python3 ./tmp/programa4.py --verbosity 1
verbosity turned on

In [16]:
!python3 ./tmp/programa4.py --help
usage: programa4.py [-h] [--verbosity VERBOSITY]

optional arguments:
  -h, --help            show this help message and exit
  --verbosity VERBOSITY
                        increase output verbosity

In [17]:
!python3 ./tmp/programa4.py --verbosity
usage: programa4.py [-h] [--verbosity VERBOSITY]
programa4.py: error: argument --verbosity: expected one argument

Se puede ver que el argumento es opcional y que al especificarlo se debe indicar un valor, de lo contrario devuelve un error. Pero en algunos casos puede que nos interese que los valores posibles sean solo TRUE o FALSE. Eso se puede implementar de la siguiente forma.

In [18]:
%load ./tmp/programa5.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")
In [19]:
!python3 ./tmp/programa5.py --verbose
verbosity turned on

In [20]:
!python3 ./tmp/programa5.py --verbose 1
usage: programa5.py [-h] [--verbose]
programa5.py: error: unrecognized arguments: 1

In [21]:
!python3 ./tmp/programa5.py --help
usage: programa5.py [-h] [--verbose]

optional arguments:
  -h, --help  show this help message and exit
  --verbose   increase output verbosity

La opción se parece más a una bandera ahora (el nombre cambió de forma acorde), y no requiere que se especifique un valor. La palabra clave action permite establecer una acción, que se ejecuta solo si el argumento de línea de comando --verbose se especifica, y que consiste en asignarle el valor True. No especificar el argumento --verbose corresponde a asignarle el valor False. Por último cabe señalar que el programa ya no es capaz de procesar un valor asociado al argumento y que la función de ayuda se ve modificada levemente.

Opciones abreviadas

Así como existe una opción abreviada -h para especificar --help es posible definir opciones abreviadas para los argumentos opcionales que uno especifica. A continuación se modifica el ejemplo anterior para hacerlo.

In [22]:
%load ./tmp/programa6.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")
In [23]:
!python3 ./tmp/programa6.py -v
verbosity turned on

In [24]:
!python3 ./tmp/programa6.py -h
usage: programa6.py [-h] [-v]

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose  increase output verbosity

Combinando argumentos posicionales y opcionales

Los argumentos posicionales y opcionales pueden combinarse, sin importar el orden en que se especifican en la invocación. El siguiente ejemplo muestra la combinación de ambos tipos de argumentos y agrega algunas variantes más elaboradas.

In [25]:
%load ./tmp/programa7.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("cuadrado", type=int,
                    help="muestra el cuadrado del numero especificado como argumento")
parser.add_argument("-v", "--verbosity", type=int,
                    help="controla la verbosidad de la salida")
args = parser.parse_args()
respuesta = args.cuadrado**2
if args.verbosity == 2:
    print("el cuadrado de {} es igual a {}".format(args.cuadrado, respuesta))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.cuadrado, respuesta))
else:
    print(respuesta)
In [26]:
!python ./tmp/programa7.py 4
16

In [27]:
!python ./tmp/programa7.py 4 -v 1
4^2 == 16

In [28]:
!python ./tmp/programa7.py 4 -v 2
el cuadrado de 4 es igual a 16

In [29]:
!python ./tmp/programa7.py -v 2 4
el cuadrado de 4 es igual a 16

In [30]:
!python ./tmp/programa7.py 4 -v 3
16

Pero hay un detalle que se hace evidente en la última invocación, ya que el valor especificado para la opción -v excede los valores previstos. Esto se puede mejorar definiendo un conjunto de valores para el argumento.

In [31]:
%load ./tmp/programa8.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("cuadrado", type=int,
                    help="muestra el cuadrado del numero especificado como argumento")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                    help="controla la verbosidad de la salida")
args = parser.parse_args()
respuesta = args.cuadrado**2
if args.verbosity == 2:
    print("el cuadrado de {} es igual a {}".format(args.cuadrado, respuesta))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.cuadrado, respuesta))
else:
    print(respuesta)
In [32]:
!python3 ./tmp/programa8.py 4 -v 2
el cuadrado de 4 es igual a 16

In [33]:
!python3 ./tmp/programa8.py 4 -v 3
usage: programa8.py [-h] [-v {0,1,2}] cuadrado
programa8.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)

In [34]:
!python3 ./tmp/programa8.py -h
usage: programa8.py [-h] [-v {0,1,2}] cuadrado

positional arguments:
  cuadrado              muestra el cuadrado del numero especificado como
                        argumento

optional arguments:
  -h, --help            show this help message and exit
  -v {0,1,2}, --verbosity {0,1,2}
                        controla la verbosidad de la salida

Y por último un ejemplo que involucra un par de argumentos posicionales y un par de argumentos opcionales.

In [35]:
%load ./tmp/programa9.py
In []:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="la base")
parser.add_argument("y", type=int, help="el exponente")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                    help="controla la verbosidad de la salida")
args = parser.parse_args()
respuesta = args.x**args.y
if args.verbosity == 2:
    print("{} elevado a la {} es igual a {}".format(args.x, args.y, respuesta))
elif args.verbosity == 1:
    print("{}^{} == {}".format(args.x, args.y, respuesta))
else:
    print(respuesta)
In [36]:
!python3 ./tmp/programa9.py 
usage: programa9.py [-h] [-v {0,1,2}] x y
programa9.py: error: too few arguments

In [37]:
!python3 ./tmp/programa9.py -h
usage: programa9.py [-h] [-v {0,1,2}] x y

positional arguments:
  x                     la base
  y                     el exponente

optional arguments:
  -h, --help            show this help message and exit
  -v {0,1,2}, --verbosity {0,1,2}
                        controla la verbosidad de la salida

In [38]:
!python3 ./tmp/programa9.py 4 2 -v 1
4^2 == 16

In [39]:
!python3 ./tmp/programa9.py 4 2 -v 2
4 elevado a la 2 es igual a 16


Ejercicio 9.1

Modifique el Ejercicio 8.1 (y 8.1b) para la escritura de archivos de modos que el nombre de archivo de salida sea un argumento posicional y el tipo de archivo de salida (.sco y .csv) se controle mediante un argumento opcional (escribir un .sco por defecto).