Ejecutando el comando de shell y capturando la salida

Quiero escribir una función que ejecute un comando de shell y devuelva su salida como una cadena , no importa, es un mensaje de error o éxito. Solo quiero obtener el mismo resultado que habría obtenido con la línea de comandos.

¿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' 

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

 >>> 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 solo progtwig que toma solo argumentos como entrada. 1 Devuelve el resultado exactamente como se imprimió a la stdout . Si necesita escribir información en la entrada stdin , vaya Popen secciones Popen o Popen . Si desea ejecutar comandos de shell complejos, vea la nota en shell=True al final de esta respuesta.

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

Versiones modernas de Python (3.5 o superior): run

Si está utilizando Python 3.5 o superior y no necesita compatibilidad con versiones anteriores , se recomienda la nueva función de run . Proporciona una API muy general de alto nivel para el módulo de subprocess . Para capturar la salida de un progtwig, pase el indicador subprocess.PIPE al argumento de la palabra clave stdout . Luego acceda al atributo stdout del objeto CompletedProcess devuelto:

 >>> 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 objeto de bytes , por lo que si desea una cadena adecuada, deberá decode . 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' 

Todo esto se puede comprimir en una sola línea:

 >>> 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 desea pasar la entrada a la stdin del proceso, pase un objeto de bytes al argumento de la palabra clave de input :

 >>> 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 (capture to result.stderr ) o stderr=subprocess.STDOUT (capture to result.stdout junto con la salida normal). Cuando la seguridad no es un problema, 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 antigua forma de hacer las cosas. Pero creo que vale la pena: ahora puede hacer casi cualquier cosa que necesite hacer solo con la función run .

Versiones anteriores de Python (2.7-3.4): check_output

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

 subprocess.check_output(*popenargs, **kwargs) 

Toma los mismos argumentos que Popen (ver más abajo) y devuelve una cadena que contiene la salida del progtwig. El comienzo de esta respuesta tiene un ejemplo de uso más detallado.

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

Si necesita canalizar desde stderr o pasar la entrada al proceso, check_output no estará a la altura de la tarea. Vea los ejemplos de Popen continuación en ese caso.

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

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

El constructor de Popen acepta un solo comando sin argumentos, o una lista que contenga un comando como su primer elemento, seguido de cualquier número de argumentos, cada uno como un elemento separado en la lista. shlex.split puede ayudar a analizar cadenas en listas con el formato adecuado. Popen objetos Popen también aceptan una gran cantidad de argumentos diferentes para la gestión de E / S de proceso y la configuración de bajo nivel.

Para enviar entrada y capturar salida, la 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 establece 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 

Tenga en cuenta la respuesta de Aaron Hall , que indica que en algunos sistemas, es posible que deba configurar stdout , stderr y stdin all en PIPE (o DEVNULL ) para que la communicate funcione.

En algunos casos raros, es posible que necesite una captura de salida compleja y en tiempo real. La respuesta de Vartec sugiere un camino a seguir, pero los métodos distintos a la communicate son propensos a los puntos muertos si no se usan con cuidado.

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

Notas

1. Ejecutando comandos de shell: el shell=True argumento shell=True

Normalmente, cada llamada a run , check_output o el constructor de Popen ejecuta un solo progtwig . Eso significa que no hay tubos de estilo bash de lujo. Si desea ejecutar comandos de shell complejos, puede pasar shell=True , que admiten las tres funciones.

Sin embargo, hacerlo plantea problemas de seguridad . Si está haciendo algo más que scripts ligeros, 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 tuberías es fuerte; resistelo. De lo contrario, es probable que veas puntos muertos o que tengas que hacer cosas piratas como esta .

2. Consideraciones Unicode.

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

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 (return_value, output)

Esto solo funciona en python2.7 : no está disponible en python3 . Para una solución que funcione en ambos, use el módulo de subprocess lugar:

 import subprocess output=subprocess.Popen(["date"],stdout=PIPE) response=output.communicate() print response 

Algo como eso:

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

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

Esta función produce línea por línea a medida que vienen (normalmente, tendría que esperar a que finalice el subproceso para obtener el resultado completo).

Para su caso el uso sería:

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

La respuesta de Vartec no lee todas las líneas, así que hice una versión que lo hizo:

 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) 

Esta es una solución complicada pero súper simple 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 en ella la salida deseada.

Nota adicional 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') 

En Python 3.5:

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

Tuve el mismo problema Pero descubrí una manera muy simple de hacer esto, sigue esto

 import subprocess output = subprocess.getoutput("ls -l") print(output) 

Espero que ayude

Nota: esta solución es específica de python3 ya que subprocess.getoutput() no funciona en python2

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

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

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

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

Su millaje puede variar, intenté el giro de @ senderle en la solución de Vartec en Windows en Python 2.6.5, pero estaba recibiendo errores y ninguna otra solución funcionó. Mi error fue: WindowsError: [Error 6] The handle is invalid .

Descubrí que tenía que asignar PIPE a cada identificador para obtener la salida que esperaba, lo siguiente me funcionó.

 import subprocess def run_command(cmd): """given shell command, returns communication tuple of stdout and stderr""" return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE).communicate() 

y llame así, ( [0] obtiene el primer elemento de la tupla, stdout ):

 run_command('tracert 11.1.0.1')[0] 

Después de aprender más, creo que necesito estos argumentos de canalización porque estoy trabajando en un sistema personalizado que usa diferentes manejadores, así que tuve que controlar directamente todos los estándares.

Para detener las ventanas emergentes de la consola (con Windows), haga lo siguiente:

 def run_command(cmd): """given shell command, returns communication tuple of stdout and stderr""" # instantiate a startupinfo obj: startupinfo = subprocess.STARTUPINFO() # set the use show window flag, might make conditional on being in Windows: startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # pass as the startupinfo keyword argument: return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, startupinfo=startupinfo).communicate() run_command('tracert 11.1.0.1') 

Tuve un sabor ligeramente diferente del mismo problema con los siguientes requisitos:

  1. Capture y devuelva los mensajes STDOUT a medida que se acumulan en el búfer STDOUT (es decir, en tiempo real).
    • @vartec resolvió este Pythonically con su uso de generadores y el ‘rendimiento’
      palabra clave arriba
  2. Imprima todas las líneas de STDOUT ( incluso si el proceso sale antes de que se pueda leer por completo el búfer de STDOUT )
  3. No desperdicie los ciclos de CPU sondeando el proceso a alta frecuencia
  4. Compruebe el código de retorno del subproceso
  5. Imprima STDERR (separado de STDOUT) si obtenemos un código de retorno de error distinto de cero.

He combinado y ajustado las respuestas anteriores para llegar a lo siguiente:

 import subprocess from time import sleep def run_command(command): p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) # Read stdout from subprocess until the buffer is empty ! for line in iter(p.stdout.readline, b''): if line: # Don't print blank lines yield line # This ensures the process has completed, AND sets the 'returncode' attr while p.poll() is None: sleep(.1) #Don't waste CPU-cycles # Empty STDERR buffer err = p.stderr.read() if p.returncode != 0: # The run_command() function is responsible for logging STDERR print("Error: " + str(err)) 

Este código se ejecutaría igual que las respuestas anteriores:

 for line in run_command(cmd): print(line) 

Si necesita ejecutar un comando de shell en varios archivos, esto fue el truco para mí.

 import os import subprocess # Define a function for running commands and capturing stdout line by line # (Modified from Vartec's solution because it wasn't printing all lines) def runProcess(exe): p = subprocess.Popen(exe, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return iter(p.stdout.readline, b'') # Get all filenames in working directory for filename in os.listdir('./'): # This command will be run on each file cmd = 'nm ' + filename # Run the command and capture the output line by line. for line in runProcess(cmd.split()): # Eliminate leading and trailing whitespace line.strip() # Split the output output = line.split() # Filter the output and print relevant lines if len(output) > 2: if ((output[2] == 'set_program_name')): print filename print line 

Edición: Acabo de ver la solución de Max Persson con la sugerencia de JF Sebastian. Se adelantó e incorporó eso.

Dividir el comando inicial para el subprocess puede ser complicado y engorroso.

Utilice shlex.split para ayudarse a sí mismo.

Comando de muestra

git log -n 5 --since "5 years ago" --until "2 year ago"

El código

 from subprocess import check_output from shlex import split res = check_output(split('git log -n 5 --since "5 years ago" --until "2 year ago"')) print(res) >>> b'commit 7696ab087a163e084d6870bb4e5e4d4198bdc61a\nAuthor: Artur Barseghyan...' 

por ejemplo, ejecute (‘ls -ahl’) diferencie tres / cuatro posibles retornos y plataformas de SO:

  1. No hay salida, pero se ejecuta con éxito
  2. línea vacía de salida, ejecutar con éxito
  3. ejecución fallida
  4. salir algo, correr con éxito

función abajo

 def execute(cmd, output=True, DEBUG_MODE=False): """Executes a bash command. (cmd, output=True) output: whether print shell output to screen, only affects screen display, does not affect returned values return: ...regardless of output=True/False... returns shell output as a list with each elment is a line of string (whitespace stripped both sides) from output could be [], ie, len()=0 --> no output; [''] --> output empty line; None --> error occured, see below if error ocurs, returns None (ie, is None), print out the error message to screen """ if not DEBUG_MODE: print "Command: " + cmd # https://stackoverflow.com/a/40139101/2292993 def _execute_cmd(cmd): if os.name == 'nt' or platform.system() == 'Windows': # set stdin, out, err all to PIPE to get results (other than None) after run the Popen() instance p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) else: # Use bash; the default is sh p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable="/bin/bash") # the Popen() instance starts running once instantiated (??) # additionally, communicate(), or poll() and wait process to terminate # communicate() accepts optional input as stdin to the pipe (requires setting stdin=subprocess.PIPE above), return out, err as tuple # if communicate(), the results are buffered in memory # Read stdout from subprocess until the buffer is empty ! # if error occurs, the stdout is '', which means the below loop is essentially skipped # A prefix of 'b' or 'B' is ignored in Python 2; # it indicates that the literal should become a bytes literal in Python 3 # (eg when code is automatically converted with 2to3). # return iter(p.stdout.readline, b'') for line in iter(p.stdout.readline, b''): # # Windows has \r\n, Unix has \n, Old mac has \r # if line not in ['','\n','\r','\r\n']: # Don't print blank lines yield line while p.poll() is None: sleep(.1) #Don't waste CPU-cycles # Empty STDERR buffer err = p.stderr.read() if p.returncode != 0: # responsible for logging STDERR print("Error: " + str(err)) yield None out = [] for line in _execute_cmd(cmd): # error did not occur earlier if line is not None: # trailing comma to avoid a newline (by print itself) being printed if output: print line, out.append(line.strip()) else: # error occured earlier out = None return out else: print "Simulation! The command is " + cmd print ""