Ejecutar comandos de Linux mediante Python

1429409720_application-x-pythonComo opinión personal, y sin que sea el motivo de crear una disputa entre unos y otros, creo que PHP es más tolerante a errores que Python. Claro, que digo esto porque llevo únicamente un par de semanas dedicado a Python, y aunque como lenguaje de programación me parece sencillo de usar y aprender, creo que escupe errores constantemente, y que a veces encontrar el error no es tan intuitivo como pudiera parecer.

Pero una vez mencionada mi opinión personal, es hora de ponerme al tema del post. Resulta que he encontrado como incentivo para aprender Python el uso de este lenguaje en todos mis scripts de shell que tengo para mantenimiento de mis sistemas. En ocasiones, me doy cuenta que lo que en un script de shell me lleva únicamente 4-5 líneas, en Python me lleva casi el triple, y sin dejar de escupirme errores en la consola. Visto desde el lado bueno, no sólo aprendo a usar Python, sino que si en alguna ocasión necesito complicar el script, posiblemente Python me ofrezca más potencia para hacerlo.

He comenzado a pasar mis scripts a Python, pero sin fijarme en la versión del mismo. Resulta que cuando me di cuenta, estaba programando en Python 2, cuando prácticamente todo el mundo programa ya en Python 3. Esto es importante porque el módulo que se utiliza para usar la línea de comandos está obsoleto en Python 3. Aun así, lo explicaré para ambas versiones.

Python 2

Para Python 2, el módulo que he estado utilizando, por resultarme más fácil y más intuitivo, es commands, cuyo uso es muy sencillo:

import commands
commands.getoutput('df -h')

Como se ve, la línea de comandos es tal cual la escribimos en GNU/Linux, y el resultado en pantalla será el mismo que obtenemos desde la consola. Aún podemos exprimir más el módulo. Al ejecutar lo siguiente:

import commands
commands.getoutput('ls readme.md')

Lo que nos aparece es :

'readme.md'

Pero podemos además obtener una tupla: el mensaje de error o del status, y el resultado o output del comando:

import commands
commands.getstatusoutput('ls readme.md')
(0, 'readme.md')

Para obtener un status de error:

import commands
commands.getstatusoutput('ls readme.txt')
(512, 'readme.txt: No such file or directory')

A mi no se me ocurre qué podríamos hacer con una tupla sí, pero iPako sí. Se le ha ocurrido un script así:

import commands
res = commands.getstatusoutput('ls readme.txt')
if res[0] == 0:
 print res[1]
else:
 print "Error: "+ str(res[0])
 print "Descripción: " + res[1]
Error: 512
Descripción: readme.txt: No such file or directory

Al fin y al cabo, es una tupla, y se puede acceder a cada uno de los valores como si fuese un array. En la documentación de Python hay más información, si bien se insiste en que es un módulo obsoleto y se recomiendo usar subprocess.

Python 3

No hay duda: hay que migrar todo a Python 3, y así lo haré yo también, ahora que estoy a tiempo y que puedo modificar rápidamente mis scripts. Para leer líneas de comandos, se utiliza el módulo subprocess, cuyo uso es algo más complicado y prolijo que commands, aunque posiblemente sea más potente. El módulo commands es muy fácil y muy intuitivo, pero ha sido retirado de Python 3.

import subprocess
subprocess.call(['ls', '-l'])

Como vemos, es bastante más complicado. ¿Corchetes? ¿Una coma para separar los argumentos? La documentación de Python 3 lo considera el uso estándar y recomendado, pero asumimos que cuando el comando es más complicado (más largo), la línea de Python puede ser un dolor de cabeza.

El uso de los corchetes y de la coma tiene una explilcación lógica: tenemos que suministrar una lista como argumento:

lista_1 = ['azul', 'rojo', 'verde']
lista_2 = ['sudo', 'apt-get', 'install', 'python3', '-y']

Existe, no obstante, una manera sencilla:

subprocess.call('ls -l', shell=True)
subprocess.call('sudo apt-get install python3 -y', shell=True)

Esto último produce los mismos resultados que la línea de antes, pero en la documentación de Python 3 se nos previene: es una práctica insegura. Afortunadamente, en la misma documentación se nos advierte de un truco, sobre todo para comandos largos y complicados: el uso de shlex.split(). Esta función convierte en una lista toda string que le pasemos:

lista1 = 'ls -l'
lista2 = shlex.split(lista1)
print(lista2)
['ls', '-l']

Con lo cual ya podremos hacer algo así:

import shlex, subprocess
subprocess.call(shlex.split('ls -l'))

O así:

import shlex, subprocess
command_line = 'sudo apt-get install python3 -y'
args = shlex.split(command_line)
subprocess.call(args)

En Dive into Python3 hay unas recomendaciones interesantes sobre cómo portar código de Python2 a Python3.

5 opiniones en “Ejecutar comandos de Linux mediante Python”

  1. Aveces el subprocess.call() falla. Es mejor utilizar os.system() directamente, así no hace falta que utilices la función split() para pasar los strings a list.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *