Pregunta Ejecutando comandos de shell desde Python y capturando la salida


Quiero escribir una función que ejecutará un comando de shell y devolverá su resultado como una cadena, no importa, ¿es un mensaje de error o de éxito? Solo quiero obtener el mismo resultado que hubiera obtenido con la línea de comando.

¿Cuál sería un ejemplo de código que haría tal cosa?

Por ejemplo:

def run_command(cmd):
    # ??????

print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'

598
2018-01-21 14:55


origen


Respuestas:


La respuesta a esta pregunta depende de la versión de Python que esté utilizando. El enfoque más simple es usar el subprocess.check_output función:

>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

check_output ejecuta un único programa que solo toma argumentos como entrada.1 Devuelve el resultado exactamente como se imprimió para stdout. Si necesita escribir una entrada para stdin, salte a la run o Popen secciones Si desea ejecutar comandos de shell complejos, vea la nota en shell=True al final de esta respuesta.

los check_output La función funciona en casi todas las versiones de Python que todavía están en uso amplio (2.7+).2 Pero para versiones más recientes, ya no es el enfoque recomendado.

Versiones modernas de Python (3.5 o superior): run

Si estás usando Python 3.5 o más alto, y no necesita compatibilidad hacia atrás, el nuevo run función es recomendado. Proporciona una API muy general de alto nivel para subprocess módulo. Para capturar la salida de un programa, pase el subprocess.PIPE bandera a la stdout argumento de palabra clave Luego acceda al stdout atributo de lo devuelto CompletedProcess objeto:

>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

El valor de retorno es un bytes objeto, así que si quieres una cadena adecuada, necesitarás decode eso. Suponiendo que el proceso llamado devuelve una cadena codificada en UTF-8:

>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

Esto se puede comprimir en un solo trazador de líneas:

>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

Si quieres pasar la información al proceso stdinpasa un bytes objetar a la input argumento de palabra clave:

>>> cmd = ['awk', 'length($0) > 5']
>>> input = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=input)
>>> result.stdout.decode('utf-8')
'foofoo\n'

Puede capturar errores pasando stderr=subprocess.PIPE (capturar a result.stderr) o stderr=subprocess.STDOUT (capturar a result.stdout junto con salida regular). Cuando la seguridad no es una preocupación, también puede ejecutar comandos de shell más complejos pasando shell=True como se describe en las notas a continuación.

Esto agrega solo un poco de complejidad, en comparación con la forma anterior de hacer las cosas. Pero creo que vale la pena el pago: ahora puedes hacer casi todo lo que necesites hacer con el run función solo.

Versiones anteriores de Python (2.7-3.4): check_output

Si está utilizando una versión anterior de Python, o necesita una compatibilidad hacia atrás modesta, probablemente pueda usar la check_output funcionar como se describe brevemente arriba. Ha estado disponible desde Python 2.7.

subprocess.check_output(*popenargs, **kwargs)  

Toma los mismos argumentos que Popen (ver a continuación), y devuelve una cadena que contiene la salida del programa. El comienzo de esta respuesta tiene un ejemplo de uso más detallado.

Puedes pasar stderr=subprocess.STDOUT para garantizar que los mensajes de error se incluyan en la salida devuelta, pero que no pasen stderr=subprocess.PIPE a check_output. Puede causar puntos muertos. Cuando la seguridad no es una preocupación, también puede ejecutar comandos de shell más complejos pasando shell=True como se describe en las notas a continuación.

Si necesita pasar de tubería stderr o pasar la entrada al proceso, check_output no estará a la altura de la tarea. Ver el Popen ejemplos a continuación en ese caso.

Aplicaciones complejas y versiones heredadas de Python (2.6 y siguientes): Popen

Si necesita una compatibilidad profunda hacia atrás, o si necesita una funcionalidad más sofisticada que check_output proporciona, tendrás que trabajar directamente con Popen objetos, que encapsulan la API de bajo nivel para subprocesos.

los Popen el constructor acepta cualquiera un solo comando sin argumentos, o una lista que contiene un comando como primer elemento, seguido de cualquier cantidad de argumentos, cada uno como un elemento separado en la lista. shlex.split puede ayudar a analizar cadenas en listas formateadas apropiadamente. Popen los objetos también aceptan una host de diferentes argumentos para la gestión de IO de proceso y configuración de bajo nivel.

Para enviar entrada y capturar salida, communicate es casi siempre el método preferido. Como en:

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]

O

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE, 
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

Si configura stdin=PIPE, communicate también le permite pasar datos al proceso a través de stdin:

>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

Nota La respuesta de Aaron Hall, que indica que en algunos sistemas, es posible que deba configurar stdout, stderry stdin todos PIPE (o DEVNULL) Llegar communicate para trabajar en absoluto.

En algunos casos excepcionales, puede necesitar una captura de salida compleja en tiempo real. VartecLa respuesta sugiere un camino a seguir, pero los métodos que no sean communicate son propensos a bloqueos si no se usan con cuidado.

Al igual que con todas las funciones anteriores, cuando la seguridad no es una preocupación, puede ejecutar comandos de shell más complejos pasando shell=True.

Notas

1. Ejecución de comandos de shell: el shell=True argumento

Normalmente, cada llamada a run, check_output, o el Popen constructor ejecuta un solo programa. Eso significa que no hay cañerías lujosas estilo bash. Si desea ejecutar comandos de shell complejos, puede pasar shell=True, que las tres funciones admiten.

Sin embargo, hacerlo aumenta preocupaciones de seguridad. Si está haciendo algo más que secuencias de comandos ligeras, es mejor que llame a cada proceso por separado y pase la salida de cada uno como una entrada al siguiente, a través de

run(cmd, [stdout=etc...], input=other_output)

O

Popen(cmd, [stdout=etc...]).communicate(other_output)

La tentación de conectar directamente las tuberías es fuerte; resistelo. De lo contrario, es probable que veas deadlocks o que tengas que hacer cosas como Hacky esta.

2. Consideraciones Unicode

check_output devuelve una cadena en Python 2, pero una bytes objeto en Python 3. Vale la pena tomarse un momento para aprender sobre Unicode si aún no lo has hecho


702
2018-01-21 15:27



Esto es mucho más fácil, pero solo funciona en Unix (incluido Cygwin).

import commands
print commands.getstatusoutput('wc -l file')

devuelve una tupla con el (return_value, output)


171
2018-02-13 19:41



Algo como eso:

def runProcess(exe):    
    p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    while(True):
      retcode = p.poll() #returns None while subprocess is running
      line = p.stdout.readline()
      yield line
      if(retcode is not None):
        break

Tenga en cuenta que estoy redireccionando stderr a stdout, puede que no sea exactamente lo que quiere, pero también quiero mensajes de error.

Esta función cede línea por línea como vienen (normalmente tendrías que esperar a que el subproceso termine para obtener la salida como un todo).

Para su caso, el uso sería:

for line in runProcess('mysqladmin create test -uroot -pmysqladmin12'.split()):
    print line,

97
2018-01-21 15:02



Vartec la respuesta no lee todas las líneas, así que hice una versión que sí:

def run_command(command):
    p = subprocess.Popen(command,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.STDOUT)
    return iter(p.stdout.readline, b'')

El uso es el mismo que la respuesta aceptada:

command = 'mysqladmin create test -uroot -pmysqladmin12'.split()
for line in run_command(command):
    print(line)

55
2017-10-30 09:24



Esto es un difícil pero super simple solución que funciona en muchas situaciones:

import os
os.system('sample_cmd > tmp')
print open('tmp', 'r').read()

Se crea un archivo temporal (aquí es tmp) con la salida del comando y puede leer su resultado deseado.

Nota extra de los comentarios: Puede eliminar el archivo tmp en el caso de un trabajo de una sola vez. Si necesita hacer esto varias veces, no es necesario eliminar el tmp.

os.remove('tmp')

33
2017-08-21 21:28



Solución moderna de Python (> = 3.1):

 res = subprocess.check_output(lcmd, stderr=subprocess.STDOUT)

12
2018-04-20 20:30



En Python 3.5:

import subprocess

output = subprocess.run("ls -l", shell=True, stdout=subprocess.PIPE, 
                        universal_newlines=True)
print(output.stdout)

11
2017-07-29 20:03



Puede usar los siguientes comandos para ejecutar cualquier comando de shell. Los he usado en ubuntu.

import os
os.popen('your command here').read()

11
2018-04-04 19:08