Ejecutando comandos Bash en Python

En mi máquina local, ejecuto un script de Python que contiene esta línea

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt" os.system(bashCommand) 

Esto funciona bien.

Luego ejecuto el mismo código en un servidor y aparece el siguiente mensaje de error

 'import site' failed; use -v for traceback Traceback (most recent call last): File "/usr/bin/cwm", line 48, in  from swap import diag ImportError: No module named swap 

Entonces, lo que hice entonces es que inserté un “print bashCommand” que me indica el comando en el terminal antes de ejecutarlo con os.system ().

Por supuesto, recibo nuevamente el error (causado por os.system (bashCommand)) pero antes de ese error imprime el comando en el terminal. Luego copié esa salida e hice una copia pegada en el terminal y presioné Enter y funciona …

¿Alguien tiene una pista de lo que está pasando?

No utilice os.system . Ha sido desaprobado en favor del subproceso . De los documentos : “Este módulo pretende reemplazar varios módulos y funciones más antiguos: os.system , os.spawn “.

Como en tu caso:

 bashCommand = "cwm --rdf test.rdf --ntriples > test.nt" import subprocess process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE) output, error = process.communicate() 

Para ampliar un poco las respuestas anteriores aquí, hay una serie de detalles que comúnmente se pasan por alto.

  • Prefiera subprocess.run() sobre subprocess.check_call() y amigos sobre subprocess.call() sobre subprocess.Popen() sobre os.system() sobre os.popen()
  • Comprenda y probablemente use text=True , también conocido como universal_newlines=True .
  • Comprenda el significado de shell=True o shell=False y cómo cambia la oferta y la disponibilidad de las conveniencias de shell.
  • Entender las diferencias entre sh y Bash
  • Comprenda cómo un subproceso está separado de su padre y, por lo general, no puede cambiarlo.
  • Evite ejecutar el intérprete de Python como un subproceso de Python.

Estos temas se tratan con más detalle a continuación.

Prefiera subprocess.run() o subprocess.check_call()

La función subprocess.Popen() es un caballo de batalla de bajo nivel, pero es difícil de usar correctamente y terminas copiando / pegando varias líneas de código … que convenientemente ya existen en la biblioteca estándar como un conjunto de envoltorios de nivel superior Funciones para diversos fines, que se presentan con más detalle a continuación.

Aquí hay un párrafo de la documentación :

El enfoque recomendado para invocar subprocesos es usar la función run() para todos los casos de uso que pueda manejar. Para casos de uso más avanzados, la interfaz Popen subyacente se puede utilizar directamente.

Desafortunadamente, la disponibilidad de estas funciones de envoltura difiere entre las versiones de Python.

  • subprocess.run() se introdujo oficialmente en Python 3.5. Está destinado a reemplazar todo lo siguiente.
  • subprocess.check_output() se introdujo en Python 2.7 / 3.1. Es básicamente equivalente a subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call() se introdujo en Python 2.5. Es básicamente equivalente a subprocess.run(..., check=True)
  • subprocess.call() se introdujo en Python 2.4 en el módulo de subprocess original ( PEP-324 ). Es básicamente equivalente a subprocess.run(...).returncode

API de alto nivel vs subprocess.Popen()

El subprocess.run() refactorizado y extendido es más lógico y más versátil que las antiguas funciones heredadas que reemplaza. Devuelve un objeto CompletedProcess que tiene varios métodos que le permiten recuperar el estado de salida, la salida estándar y algunos otros resultados e indicadores de estado del subproceso terminado.

subprocess.run() es el camino a seguir si simplemente necesita un progtwig para ejecutar y devolver el control a Python. Para escenarios más complicados (procesos en segundo plano, tal vez con E / S interactiva con el progtwig para padres de Python), aún necesita usar subprocess.Popen() y cuidarse por sí mismo. Esto requiere una comprensión bastante compleja de todas las partes móviles y no debe realizarse a la ligera. El objeto Popen más Popen representa el proceso (posiblemente en ejecución) que debe gestionarse desde su código durante el rest de la vida útil del subproceso.

Tal vez debería enfatizarse que solo subprocess.Popen() simplemente crea un proceso. Si lo deja así, tiene un subproceso que se ejecuta simultáneamente con Python, por lo que se trata de un proceso de “fondo”. Si no necesita hacer entrada o salida o coordinar con usted, puede hacer un trabajo útil en paralelo con su progtwig Python.

Evita os.system() y os.popen()

Desde el tiempo eterno (bueno, desde Python 2.5) la documentación del módulo os ha contenido la recomendación de preferir el subprocess sobre os.system() :

El módulo de subprocess proporciona instalaciones más potentes para generar nuevos procesos y recuperar sus resultados; Usar ese módulo es preferible a usar esta función.

Los problemas con el system() son que obviamente depende del sistema y no ofrece formas de interactuar con el subproceso. Simplemente se ejecuta, con salida estándar y error estándar fuera del scope de Python. La única información que recibe Python es el estado de salida del comando (cero significa éxito, aunque el significado de valores distintos de cero también depende del sistema).

PEP-324 (que ya se mencionó anteriormente) contiene una explicación más detallada de por qué os.system es problemático y cómo el subprocess intenta resolver esos problemas.

os.popen() solía estar incluso más fuertemente desanimado :

En desuso desde la versión 2.6: esta función está obsoleta. Utilizar el módulo de subprocess .

Sin embargo, desde algún momento en Python 3, se ha vuelto a implementar para usar simplemente el subprocess y se redirige a la documentación del subprocess.Popen() para obtener más detalles.

Comprender y usar usualmente check=True

También notará que subprocess.call() tiene muchas de las mismas limitaciones que os.system() . En uso regular, generalmente debe verificar si el proceso finalizó con éxito, lo que hacen subprocess.check_call() y subprocess.check_output() (donde este último también devuelve la salida estándar del subproceso terminado). De manera similar, generalmente debe usar check=True con subprocess.run() menos que específicamente necesite permitir que el subproceso devuelva un estado de error.

En la práctica, con check=True o subprocess.check_* , Python lanzará una excepción CalledProcessError si el subproceso devuelve un estado de salida distinto de cero.

Un error común con subprocess.run() es omitir check=True y sorprenderse cuando el código de abajo falla si el subproceso falla.

Por otro lado, un problema común con check_call() y check_output() fue que los usuarios que utilizaron estas funciones a ciegas se sorprendieron cuando se generó la excepción, por ejemplo, cuando grep no encontró una coincidencia. (Probablemente debería reemplazar grep con el código nativo de Python, como se describe a continuación).

Todas las cosas contadas, debe comprender cómo los comandos de shell devuelven un código de salida y bajo qué condiciones devolverán un código de salida distinto de cero (error), y tomar una decisión consciente sobre cómo debe manejarse exactamente.

Comprender y probablemente usar text=True también conocido como universal_newlines=True

Desde Python 3, las cadenas internas de Python son cadenas Unicode. Pero no hay garantía de que un subproceso genere una salida de Unicode, o cadenas en absoluto.

(Si las diferencias no son evidentes de inmediato, se recomienda la lectura del Pragmático Unicode de Ned Batchelder. Si no es totalmente obligatorio, hay una presentación de video de 36 minutos si lo prefiere, aunque leer la página probablemente le llevará mucho menos tiempo. )

En el fondo, Python tiene que buscar un búfer de bytes e interpretarlo de alguna manera. Si contiene un blob de datos binarios, no debe decodificarse en una cadena Unicode, porque es un comportamiento propenso a errores e inductor de errores, precisamente el tipo de comportamiento molesto que invadió muchos scripts de Python 2, antes de que hubiera una manera de Distinguir adecuadamente entre texto codificado y datos binarios.

Con text=True , le dices a Python que, de hecho, esperas datos textuales en la encoding predeterminada del sistema, y ​​que debe decodificarse en una cadena Python (Unicode) con la mejor habilidad de Python (generalmente UTF-8 en cualquier Sistema moderadamente actualizado, excepto quizás Windows?)

Si eso no es lo que solicitas, Python solo te dará cadenas de bytes en las cadenas stdout y stderr . Tal vez en algún momento posterior sepa que, después de todo, eran cadenas de texto y que conoce su encoding. Entonces, puedes decodificarlos.

 normal = subprocess.run([external, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True) print(normal.stdout) convoluted = subprocess.run([external, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) # You have to know (or guess) the encoding print(convoluted.stdout.decode('utf-8')) 

Python 3.7 introdujo el text alias más corto, más descriptivo y comprensible para el argumento de la palabra clave que anteriormente se denominaba de alguna manera erróneamente universal_newlines .

Comprender shell=True vs shell=False

Con shell=True , pasas una sola cadena a tu shell, y la shell lo toma desde allí.

Con shell=False pasa una lista de argumentos al sistema operativo, omitiendo el shell.

Cuando no tienes un shell, guardas un proceso y te deshaces de una cantidad considerable de complejidad oculta, que puede o no albergar errores o incluso problemas de seguridad.

Por otro lado, cuando no tiene un shell, no tiene redirección, expansión de comodines, control de trabajos y una gran cantidad de otras características de shell.

Un error común es usar shell=True y luego pasarle a Python una lista de tokens, o viceversa. Esto sucede que funciona en algunos casos, pero está realmente mal definido y podría romperse de maneras interesantes.

 # XXX AVOID THIS BUG buggy = subprocess.run('dig +short stackoverflow.com') # XXX AVOID THIS BUG TOO broken = subprocess.run(['dig', '+short', 'stackoverflow.com'], shell=True) # XXX DEFINITELY AVOID THIS pathological = subprocess.run(['dig +short stackoverflow.com'], shell=True) correct = subprocess.run(['dig', '+short', 'stackoverflow.com'], # Probably don't forget these, too check=True, text=True) # XXX Probably better avoid shell=True # but this is nominally correct fixed_but_fugly = subprocess.run('dig +short stackoverflow.com', shell=True, # Probably don't forget these, too check=True, text=True) 

La respuesta común “pero funciona para mí” no es una refutación útil a menos que usted entienda exactamente bajo qué circunstancias podría dejar de funcionar.

Ejemplo de refactorización

Muy a menudo, las características del shell se pueden reemplazar con el código nativo de Python. Los scripts simples de Awk o sed probablemente deberían traducirse simplemente a Python.

Para ilustrar parcialmente esto, aquí hay un ejemplo típico pero un poco tonto que involucra muchas características de shell.

 cmd = '''while read -rx; do ping -c 3 "$x" | grep 'round-trip min/avg/max' done  

Algunas cosas a tener en cuenta aquí:

  • Con shell=False no necesita la cita que el shell requiere alrededor de las cadenas. Poner comillas de todos modos es probablemente un error.
  • A menudo tiene sentido ejecutar el menor código posible en un subproceso. Esto le da más control sobre la ejecución desde su código Python.
  • Dicho esto, las tuberías de shell complejas son tediosas y, en ocasiones, difíciles de reimplementar en Python.

El código refactorizado también ilustra lo que realmente hace el shell por usted con una syntax muy concisa, para bien o para mal. Python dice que explícito es mejor que implícito, pero el código de Python es bastante detallado y podría decirse que parece más complejo de lo que realmente es. Por otro lado, ofrece una serie de puntos en los que puede tomar el control en medio de otra cosa, como se ilustra trivialmente por la mejora de que podemos incluir fácilmente el nombre del host junto con la salida del comando de shell. (Esto tampoco es difícil de hacer en la shell, pero a costa de otro desvío y quizás otro proceso).

Construcciones de Shell comunes

Para completar, aquí hay breves explicaciones de algunas de estas características de shell, y algunas notas sobre cómo pueden ser reemplazadas por instalaciones nativas de Python.

  • La expansión del comodín Globbing aka puede reemplazarse con glob.glob() o muy a menudo con comparaciones de cadenas Python simples como for file in os.listdir('.'): if not file.endswith('.png'): continue . Bash tiene varias otras facilidades de expansión como .{png,jpg} brace expansion y {1..100} así como tilde expansion ( ~ expande a su directorio de inicio, y más generalmente ~account al directorio de inicio de otro usuario)
  • La redirección le permite leer un archivo como entrada estándar y escribir su salida estándar en un archivo. grep 'foo' outputfile abre el outputfile de outputfile para escritura y el inputfile para lectura, y pasa su contenido como entrada estándar a grep , cuya salida estándar luego cae en el outputfile de outputfile . Esto generalmente no es difícil de reemplazar con el código nativo de Python.
  • Las tuberías son una forma de redirección. echo foo | nl echo foo | nl ejecuta dos subprocesos, donde la salida estándar de echo es la entrada estándar de nl (en el nivel del sistema operativo, en sistemas similares a Unix, este es un identificador de archivo único). Si no puede reemplazar uno o ambos extremos de la tubería con el código nativo de Python, quizás piense en usar un shell después de todo, especialmente si la tubería tiene más de dos o tres procesos (aunque mire el módulo de pipes en la biblioteca estándar de Python o una número de competidores de terceros más modernos y versátiles).
  • El control de trabajos le permite interrumpir trabajos, ejecutarlos en segundo plano, devolverlos al primer plano, etc. Por supuesto, las señales básicas de Unix para detener y continuar un proceso también están disponibles en Python. Pero los trabajos son una abstracción de nivel superior en el shell que involucra grupos de procesos, etc. que debe comprender si quiere hacer algo como esto desde Python.

Entender las diferencias entre sh y Bash

subprocess ejecuta sus comandos de shell con /bin/sh menos que solicite específicamente lo contrario (excepto, por supuesto, en Windows, donde utiliza el valor de la variable COMSPEC ). Esto significa que varias características de solo Bash como matrices, [[ etc no están disponibles.

Si necesita usar la syntax de solo Bash, puede pasar la ruta al shell como executable='/bin/bash' (donde, por supuesto, si su Bash está instalado en otro lugar, debe ajustar la ruta).

 subprocess.run(''' # This for loop syntax is Bash only for((i=1;i<=$#;i++)); do # Arrays are Bash-only array[i]+=123 done''', shell=True, check=True, executable='/bin/bash') 

Un subprocess está separado de su padre y no puede cambiarlo

Un error algo común es hacer algo como

 subprocess.run('foo=bar', shell=True) subprocess.run('echo "$foo"', shell=True) # Doesn't work 

lo que, aparte de la falta de elegancia, también revela una falta fundamental de comprensión de la "sub" parte del nombre "subproceso".

Un proceso secundario se ejecuta completamente separado de Python, y cuando finaliza, Python no tiene idea de lo que hizo (aparte de los indicadores vagos que puede inferir del estado de salida y de la salida del proceso secundario). Un niño generalmente no puede cambiar el entorno de los padres; no puede establecer una variable, cambiar el directorio de trabajo o, en muchas palabras, comunicarse con su padre sin la cooperación del padre.

La solución inmediata en este caso particular es ejecutar ambos comandos en un solo subproceso;

 subprocess.run('foo=bar; echo "$foo"', shell=True) 

aunque obviamente este caso de uso particular no requiere el shell en absoluto. Recuerde, puede manipular el entorno del proceso actual (y, por lo tanto, también sus hijos) a través de

 os.environ['foo'] = 'bar' 

o pasar una configuración de entorno a un proceso hijo con

 subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'}) 

(por no mencionar el obvio refactoring subprocess.run(['echo', 'bar']) ; pero echo es un mal ejemplo de algo para ejecutar en un subproceso en primer lugar, por supuesto).

No ejecute Python desde Python

Este es un consejo ligeramente dudoso; ciertamente hay situaciones en las que tiene sentido o incluso es un requisito absoluto ejecutar el intérprete de Python como un subproceso desde un script de Python. Pero con mucha frecuencia, el enfoque correcto es simplemente import el otro módulo de Python en su script de llamada y llamar directamente a sus funciones.

Si el otro script de Python está bajo su control, y no es un módulo, considere convertirlo en uno . (Esta respuesta ya es demasiado larga, así que no voy a profundizar en los detalles aquí).

Si necesita paralelismo, puede ejecutar funciones de Python en subprocesos con el módulo de multiprocessing . También hay threading que ejecutan múltiples tareas en un solo proceso (que es más liviano y le da más control, pero también más restringido en el hecho de que los subprocesos de un proceso están estrechamente vinculados y vinculados a un solo GIL ).

Llámalo con subproceso.

 import subprocess subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt") 

El error que está recibiendo parece ser porque no hay un módulo de intercambio en el servidor, debe instalar el intercambio en el servidor y luego ejecutar el script nuevamente.

Es posible que use el progtwig bash, con el parámetro -c para ejecutar los comandos:

 bashCommand = "cwm --rdf test.rdf --ntriples > test.nt" output = subprocess.check_output(['bash','-c', bashCommand]) 

Puedes usar ‘subproceso’, pero siempre sentí que no era una forma ‘pythonica’ de hacerlo. Así que creé Sultan (conector descarado) que facilita la ejecución de las funciones de la línea de comandos.

https://github.com/aeroxis/sultan

Según el error, le falta un paquete llamado swap en el servidor. Este /usr/bin/cwm requiere. Si estás en Ubuntu / Debian, instala python-swap usando aptitude.

Para ejecutar el comando sin shell, pase el comando como una lista e implemente la redirección en Python usando [subprocess] :

 #!/usr/bin/env python import subprocess with open('test.nt', 'wb', 0) as file: subprocess.check_call("cwm --rdf test.rdf --ntriples".split(), stdout=file) 

Nota: no > test.nt al final. stdout=file implementa la redirección.


Para ejecutar el comando usando el shell en Python, pase el comando como una cadena y habilite shell=True :

 #!/usr/bin/env python import subprocess subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt", shell=True) 

Aquí el shell es responsable de la redirección de salida ( > test.nt está en el comando).


Para ejecutar un comando de bash que usa bashisms, especifique el ejecutable de bash explícitamente, por ejemplo, para emular la sustitución del proceso de bash :

 #!/usr/bin/env python import subprocess subprocess.check_call('program <(command) <(another-command)', shell=True, executable='/bin/bash') 

También puedes usar ‘os.popen’. Ejemplo:

 import os command = os.popen('ls -al') print(command.read()) print(command.close()) 

Salida:

 total 16 drwxr-xr-x 2 root root 4096 ago 13 21:53 . drwxr-xr-x 4 root root 4096 ago 13 01:50 .. -rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py -rw-r--r-- 1 root root 77 ago 13 21:53 test.py None 

La forma en que Python hace esto es usar subprocess.Popen

subprocess.Popen toma una lista en la que el primer elemento es el comando que se va a ejecutar seguido de los argumentos de la línea de comandos.

Como ejemplo:

 import subprocess args = ['echo', 'Hello!'] subprocess.Popen(args) // same as running `echo Hello!` on cmd line args2 = ['echo', '-v', '"Hello Again"'] subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line