Línea de comandos en Python
En este artículo vamos a seguir aprendiendo Python convirtiendo, poco a poco, nuestros programas en cosas más y más útiles.
En este caso vamos a mejorar la forma en la que nuestro programa paridad.py
puede comunicarse con el sistema operativo recibiendo información a través de la línea de comandos, en lugar de utilizar simplemente la entrada estándar.
La línea de comandos es un mecanismo que el shell dispone para poder parametrizar la ejecución de los programas, introduciendo no sólo datos de entrada, sino también parámetros de comportamiento. En nuestros lenguajes de programación, esto se traduce en una librería estándar que proporciona tanto estructuras de datos, conteniendo los parámetros y argumentos del programa, como funciones para manipularlos, si ello fuese necesario.
La forma de utilizar la línea de comandos para introducir parámetros y argumentos es, simplemente, escribir todos los parámetros y argumentos después del nombre del programa a ejecutar, separados por espacios. Fíjate en la siguiente herramienta, ls
, y cómo su comportamiento cambia al introducir una serie de parámetros y argumentos:
$ ls
Applications Developer Downloads Movies Pictures Public
Desktop Documents Library Music Postman opt
$ ls -lh
total 0
drwx------@ 8 gabriel gabriel 256B 23 Feb 15:03 Applications
drwx------@ 11 gabriel gabriel 352B 13 Mar 16:20 Desktop
drwx------ 18 gabriel gabriel 576B 7 Mar 15:47 Developer
drwx------@ 10 gabriel gabriel 320B 9 Feb 10:51 Documents
drwx------@ 47 gabriel gabriel 1.5K 13 Mar 15:08 Downloads
drwx------+ 96 gabriel gabriel 3.0K 10 Mar 19:39 Library
drwx------ 4 gabriel gabriel 128B 30 Jan 14:03 Movies
drwx------+ 4 gabriel gabriel 128B 27 Jan 15:51 Music
drwx------+ 8 gabriel gabriel 256B 16 Feb 09:38 Pictures
drwxr-xr-x 3 gabriel gabriel 96B 22 Feb 14:57 Postman
drwxr-xr-x+ 4 gabriel gabriel 128B 23 Dec 09:02 Public
drwxr-xr-x 9 gabriel gabriel 288B 22 Feb 14:47 opt
$ ls -lh Desktop
total 48
-rw-r--r--@ 1 gabriel gabriel 22K 17 Feb 16:45 learnthispower.png
En el primer ejemplo, ls
(list files) presenta el nombre de los ficheros o directorios presentes en el directorio actual.
Al introducir el parámetro -lh
, que en realidad son dos, -l -h
, pero en una forma contracta, el programa cambia el formato de la salida y presenta mucha más información:
- el número total de bloques del disco ocupados por los ficheros presentes en el directorio a examinar. Un bloque en disco es un elemento físico de almacenamiento, del que hablaremos en otra ocasión.
- Para cada elemento:
- permisos, que ya veremos en detalle;
- número de enlaces existentes a ese fichero o directorio,
- el dueño y el grupo al que dicho usuario pertenece,
- el tamaño, con un sufijo para hacerlo más legible por los humanos gracias a
-h
, - el número de elementos que contiene si es un directorio, la fecha de modificación,
- y el propio elemento descrito.
Por último, si además (o en lugar de) parámetros de funcionamiento, terminamos la línea de comandos con un nombre de directorio o fichero existente como argumento, ls
se centrará en el mismo. En el caso de directorios, mostrará el contenido de dicho directorio.
Veamos la versión original de paridad.py
, omitiendo ya los comentarios explicativos:
#!/usr/bin/python3
import sys
num = int( input() )
if num % 2 == 0 :
print("par")
else :
print ("impar")
Por si no lo recuerdas, este programa acepta un número entero por la entrada estándar, ya sea de forma interactiva o utilizando el pipe, y escribe par
o impar
en la salida estándar.
Vamos a modificar el programa de acuerdo para que acepte el número a examinar como argumento, con el siguiente objetivo:
- Si el usuario no introduce ninguna información, el programa funcionará como anteriormente, utilizando la entrada estándar para obtener el número a evaluar.
- Si el usuario introduce un número a continuación, como por ejemplo
./paridad.py 45
, el programa evaluará dicho número sin hacer nada más.
Para ello, vamos a usar una estructura de datos proporcionada por la librería sys
, que es sys.argv
. En esta estructura de datos, de tipo array, o formación, tenemos lo siguiente:
- En la primera posición,
sys.argv[0]
, tenemos el nombre del programa. - En las posiciones siguientes,
sys.argv[1]
... hasta el fin delarray
tenemos todos los datos de entrada.
Prueba este programa:
#!/usr/bin/python3
import sys
for op in sys.argv:
print(op);
Si guardas ese código en un fichero, por ejemplo argv.py
y lo ejecutas este programa tras otorgarle permisos de ejecución con chmod +x argv.py
, verás lo siguiente:
$ ./argv.py
./argv.py
$ ./argv.py hola mundo
./argv.py
hola
mundo
Lo que hace el código es ejecutar un bucle for sobre sys.argv
. El bucle for se lee, literalmente: para cada elemento, op
, en sys.argv
: imprime el valor de op
.
Así pues, una solución a nuestro programa es la siguiente:
#!/usr/bin/python3
import sys
if len(sys.argv) > 1 :
num = int(sys.argv[1])
else:
num = int(input())
if num % 2 == 0 :
print("par")
else :
print ("impar")
Lo que hace el código que hemos añadido es:
- Se obtiene la longitud del array, con la función
len()
:len(sys.argv)
. - Si dicha longitud es estrictamente mayor que 1, se tomará el número a analizar de la línea de comandos.
- En caso contrario, como la longitud es igual a 1,
sys.argv
sólo contendrá el nombre del programa y por lo tanto no hay argumentos, el programa tomará el número a analizar de la entrada estándar.
Vamos a probarlo para comprobar, no sólo que hemos resuelto el problema, sino que, además, no hemos roto nada:
$ ./paridad.py
45
impar
$ echo 4 | ./paridad.py
par
$ ./paridad.py 23
impar
Como nota final, el hecho de no romper nada al cambiar un programa, esto es, conservar su funcionamiento original a través de distintas actualizaciones a lo largo de su vida útil, es una cualidad llamada compatibilidad hacia atrás.
Es una traducción tosca del inglés backwards compatibility, pero nos podemos hacer una idea de lo importante que es si, con el paso del tiempo, nuestros usuarios han construido muchos pipelines usando paridad.py
.
👉 Sigue a micromáquina en @gabriel@micromaquina.com 👉 Sigue a Gabriel Viso en @gabriel@fedi.gvisoc.com