Para ampliar un poco las respuestas anteriores aquí, hay una serie de detalles que comúnmente se pasan por alto.
- Prefiero
subprocess.run()
sobre subprocess.check_call()
y amigos sobre subprocess.call()
sobre subprocess.Popen()
sobre os.system()
sobre sobreos.popen()
- Comprender y probablemente usar
text=True
, aka universal_newlines=True
.
- Comprenda el significado de
shell=True
o shell=False
y cómo cambia las citas y la disponibilidad de las comodidades de shell.
- Comprender las diferencias entre
sh
y Bash
- Comprenda cómo un subproceso está separado de su padre y, en general, no puede cambiar el padre.
- Evite ejecutar el intérprete de Python como un subproceso de Python.
Estos temas se tratan con más detalle a continuación.
Prefiero subprocess.run()
osubprocess.check_call()
La subprocess.Popen()
función 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 funciones de envoltura de nivel superior 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 utilizar la run()
función para todos los casos de uso que pueda manejar. Para casos de uso más avanzados, la Popen
interfaz subyacente se puede usar 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()
fue introducido en Python 2.7 / 3.1. Básicamente es equivalente asubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
fue introducido en Python 2.5. Básicamente es equivalente asubprocess.run(..., check=True)
subprocess.call()
fue introducido en Python 2.4 en el subprocess
módulo original ( PEP-324 ). Básicamente es equivalente asubprocess.run(...).returncode
API de alto nivel vs subprocess.Popen()
El refactorizado y extendido subprocess.run()
es más lógico y más versátil que las funciones heredadas más antiguas que reemplaza. Devuelve un CompletedProcess
objeto 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 programa para ejecutar y devolver el control a Python. Para escenarios más complicados (procesos en segundo plano, tal vez con E / S interactivas con el programa principal Python), aún necesita usar subprocess.Popen()
y cuidar toda la tubería usted mismo. Esto requiere una comprensión bastante compleja de todas las partes móviles y no debe realizarse a la ligera. El Popen
objeto más simple representa el proceso (posiblemente aún en ejecución) que necesita ser administrado desde su código por el resto de la vida útil del subproceso.
Quizás debería enfatizarse que subprocess.Popen()
simplemente crea un proceso. Si lo deja así, tiene un subproceso ejecutándose simultáneamente con Python, por lo que se trata de un proceso de "fondo". Si no necesita hacer entrada o salida o coordinarse con usted, puede hacer un trabajo útil en paralelo con su programa Python.
Evitar os.system()
yos.popen()
Desde el tiempo eterno (bueno, desde Python 2.5) la os
documentación del módulo contiene la recomendación de preferir subprocess
sobre os.system()
:
El subprocess
módulo proporciona instalaciones más potentes para generar nuevos procesos y recuperar sus resultados; usar ese módulo es preferible a usar esta función.
El problema system()
es 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 alcance de Python. La única información que Python recibe es el estado de salida del comando (cero significa éxito, aunque el significado de los valores distintos de cero también depende en cierta medida del sistema).
PEP-324 (que ya se mencionó anteriormente) contiene una justificación más detallada de por qué os.system
es problemático y cómo subprocess
intenta resolver esos problemas.
os.popen()
solía desanimarse aún más :
En desuso desde la versión 2.6: esta función está obsoleta. Usa el subprocess
módulo.
Sin embargo, desde algún momento en Python 3, se ha vuelto a implementar simplemente para usar subprocess
y redirige a la subprocess.Popen()
documentación para obtener más detalles.
Comprender y generalmente usar check=True
También notará que subprocess.call()
tiene muchas de las mismas limitaciones que os.system()
. En el uso regular, generalmente debe verificar si el proceso finalizó con éxito, qué subprocess.check_call()
y subprocess.check_output()
qué hacer (donde este último también devuelve la salida estándar del subproceso terminado). De manera similar, generalmente debe usar check=True
con a 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 CalledProcessError
excepción si el subproceso devuelve un estado de salida distinto de cero.
Un error común subprocess.run()
es omitir check=True
y sorprenderse cuando falla el código descendente si falla el subproceso.
Por otro lado, un problema común con check_call()
y check_output()
fue que los usuarios que usaron estas funciones a ciegas se sorprendieron cuando se produjo la excepción, por ejemplo, cuando grep
no encontraron una coincidencia. (Probablemente debería reemplazar grep
con código nativo de Python de todos modos, como se describe a continuación).
Todo lo que cuenta, 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 de cómo se debe manejar exactamente.
Comprender y probablemente usar text=True
akauniversal_newlines=True
Desde Python 3, las cadenas internas de Python son cadenas Unicode. Pero no hay garantía de que un subproceso genere salida Unicode o cadenas en absoluto.
(Si las diferencias no son inmediatamente obvias, se recomienda la Unicode pragmática de Ned Batchelder , si no es absolutamente obligatoria, la lectura. Si lo prefiere, hay una presentación de video de 36 minutos detrás del enlace, aunque leer la página usted mismo probablemente tomará mucho menos tiempo. )
En el fondo, Python tiene que buscar un bytes
búfer e interpretarlo de alguna manera. Si contiene un blob de datos binarios, no debe decodificarse en una cadena Unicode, porque ese es un comportamiento propenso a errores e inductor de errores, precisamente el tipo de comportamiento molesto que acribillaba 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 codificación predeterminada del sistema, y que deben decodificarse en una cadena Python (Unicode) de la mejor manera posible de Python (generalmente UTF-8 en cualquier moderadamente hasta sistema de fechas, excepto quizás Windows?)
Si eso no es lo que solicita, Python solo le dará bytes
cadenas en las cadenas stdout
y stderr
. Quizás en algún momento posterior lo hagas sabe que eran cadenas de texto después de todo, y conoce su codificación. 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 alias más corto, más descriptivo y comprensible text
para el argumento de la palabra clave que anteriormente se llamaba de manera algo engañosa universal_newlines
.
Entender shell=True
vsshell=False
Con shell=True
usted pasa una sola cadena a su shell, y el shell lo toma desde allí.
Con shell=False
usted pasa una lista de argumentos al sistema operativo, sin pasar por el shell.
Cuando no tiene un shell, guarda un proceso y se deshace de un cantidad bastante 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 comprenda 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 código nativo de Python. Awk simple osed
scripts probablemente deberían simplemente traducirse 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 -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Algunas cosas a tener en cuenta aquí:
- Con
shell=False
usted 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 complejas de shell son tediosas y, a veces, difíciles de reimplementar en Python.
El código refactorizado también ilustra cuánto hace realmente el shell para usted con una sintaxis 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 parecer más complejo de lo que realmente es. Por otro lado, ofrece una serie de puntos en los que puede tomar el control en el medio de otra cosa, como lo ejemplifica trivialmente la mejora de que podemos incluir fácilmente el nombre del host junto con la salida del comando de shell. (Esto tampoco es un desafío en el shell, pero a expensas 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 tal vez se puedan reemplazar con las instalaciones nativas de Python.
- La expansión de comodines, también conocida como Globbing, se puede reemplazar
glob.glob()
o muy a menudo con simples comparaciones de cadenas de Python como for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash tiene varias otras instalaciones de expansión, como la .{png,jpg}
expansión de llaves y la expansión de {1..100}
tilde (se ~
expande a su directorio de inicio y, en general, ~account
al directorio de inicio de otro usuario)
- Las variables de shell como
$SHELL
o a $my_exported_var
veces simplemente se pueden reemplazar con variables de Python. Las variables de shell exportados están disponibles como por ejemplo os.environ['SHELL']
(el significado de export
es hacer que la variable disponible para subprocesos -. Una variable que no está disponible para subprocesos obviamente no estar disponibles para Python se ejecuta como un subproceso de la concha, o viceversa La env=
palabra clave El argumento de los subprocess
métodos le permite definir el entorno del subproceso como un diccionario, por lo que esa es una forma de hacer que una variable de Python sea visible para un subproceso). Con shell=False
usted deberá comprender cómo eliminar las comillas; por ejemplo, cd "$HOME"
es equivalente a os.chdir(os.environ['HOME'])
sin comillas alrededor del nombre del directorio. (Muy a menudocd
de todos modos no es útil ni necesario, y muchos principiantes omiten las comillas dobles alrededor de la variable y se salen con la suya hasta que un día ... )
- La redirección le permite leer un archivo como su entrada estándar y escribir su salida estándar en un archivo.
grep 'foo' <inputfile >outputfile
se abre outputfile
para escribir y inputfile
leer, y pasa su contenido como entrada estándar a grep
, cuya salida estándar luego aterriza outputfile
. Esto no suele ser difícil de reemplazar con código nativo de Python.
- Las tuberías son una forma de redireccionamiento.
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 tipo Unix, este es un identificador de archivo único). Si no puede reemplazar uno o ambos extremos de la tubería con 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 pipes
módulo en la biblioteca estándar de Python o un 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. Las señales básicas de Unix para detener y continuar un proceso, por supuesto, 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 desea hacer algo así desde Python.
- Citar en el shell es potencialmente confuso hasta que comprenda que todo es básicamente una cadena. Por
ls -l /
lo tanto, es equivalente a, 'ls' '-l' '/'
pero la cita alrededor de literales es completamente opcional. Las cadenas sin comillas que contienen metacaracteres de shell experimentan expansión de parámetros, tokenización de espacios en blanco y expansión de comodines; las comillas dobles evitan la tokenización de espacios en blanco y la expansión de comodines, pero permiten expansiones de parámetros (sustitución de variables, sustitución de comandos y procesamiento de barra invertida). Esto es simple en teoría, pero puede ser desconcertante, especialmente cuando hay varias capas de interpretación (un comando de shell remoto, por ejemplo).
Comprender las diferencias entre sh
y Bash
subprocess
ejecuta sus comandos de shell a /bin/sh
menos que solicite específicamente lo contrario (excepto, por supuesto, en Windows, donde utiliza el valor de la COMSPEC
variable). Esto significa que varias funciones de solo Bash, como matrices, [[
etc. , no están disponibles.
Si necesita usar la sintaxis 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')
A 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
que, aparte de la falta de elegancia, también revela una falta fundamental de comprensión de la parte "sub" 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 vagos indicadores que puede inferir del estado de salida y 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 pocas 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 en 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 secundario con
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(sin mencionar la obvia refactorización subprocess.run(['echo', 'bar'])
; pero echo
es un mal ejemplo de algo que se ejecuta en un subproceso en primer lugar, por supuesto).
No ejecutes Python desde Python
Este es un consejo ligeramente dudoso; Ciertamente, hay situaciones en las que tiene sentido o incluso es un requisito absoluto para ejecutar el intérprete de Python como un subproceso de 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 a sus funciones directamente.
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 profundizaré en los detalles aquí).
Si necesita paralelismo, puede ejecutar funciones de Python en subprocesos con el multiprocessing
módulo. También existe el threading
que ejecuta múltiples tareas en un solo proceso (que es más liviano y le brinda más control, pero también más restringido porque los hilos dentro de un proceso están estrechamente unidos y vinculados a un solo GIL ).
cwm
. ¿Quizás tiene alguna configuración en su.bashrc
que configura el entorno para el uso de bash interactivo?