Subproceso de Python / Popen con un entorno modificado

284

Creo que ejecutar un comando externo con un entorno ligeramente modificado es un caso muy común. Así es como tiendo a hacerlo:

import subprocess, os
my_env = os.environ
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

Tengo el presentimiento de que hay una mejor manera; se ve bien?

Oren_H
fuente
10
También prefiera usar en os.pathseplugar de ":" para rutas que funcionan en plataformas. Ver stackoverflow.com/questions/1499019/…
amit el
8
@phaedrus No estoy seguro de que sea muy relevante cuando usa caminos como /usr/sbin:-)
Dmitry Ginzburg el

Respuestas:

403

Creo que os.environ.copy()es mejor si no tiene la intención de modificar el entorno os.iron para el proceso actual:

import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)
Daniel Burke
fuente
>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (última llamada más reciente): Archivo "<stdin>", línea 1, en <module> TypeError: 'instancemethod' el objeto no admite la asignación de elementos
usuario1338062
55
@ user1338062 está asignando el método real os.environ.copyde la envvariable, pero es necesario asignar el resultado de una llamada al método os.environ.copy()a env.
vestido
44
La resolución de la variable de entorno solo funciona realmente si la utiliza shell=Trueen su subprocess.Popeninvocación. Tenga en cuenta que existen posibles implicaciones de seguridad al hacerlo.
danielpops
Inside subprocess.Popen (my_command, env = my_env) - que es "my_command"
avinash
@avinash: my_commandes solo un comando para ejecutar. Podría ser, por ejemplo, /path/to/your/own/programo cualquier otra declaración "ejecutable".
kajakIYD
64

Eso depende de cuál sea el problema. Si es para clonar y modificar el entorno, una solución podría ser:

subprocess.Popen(my_command, env=dict(os.environ, PATH="path"))

Pero eso depende en cierta medida de que las variables reemplazadas sean identificadores válidos de Python, que lo son con mayor frecuencia (¿con qué frecuencia se encuentra con nombres de variables de entorno que no son alfanuméricos + guiones bajos o variables que comienzan con un número?).

De lo contrario, podría escribir algo como:

subprocess.Popen(my_command, env=dict(os.environ, 
                                      **{"Not valid python name":"value"}))

En el caso muy extraño (¿con qué frecuencia usa códigos de control o caracteres no ascii en los nombres de las variables de entorno?) Que las claves del entorno son bytesque no puede (en python3) incluso usar esa construcción.

Como puede ver, las técnicas (especialmente la primera) que se usan aquí, los beneficios en las claves del entorno normalmente son identificadores de Python válidos, y también conocidos de antemano (en el momento de la codificación), el segundo enfoque tiene problemas. En los casos en que ese no sea el caso, probablemente debería buscar otro enfoque .

Rey del cielo
fuente
3
voto a favor No sabía que podías escribir dict(mapping, **kwargs). Pensé que era cualquiera o. Nota: se copia os.environsin modificarlo como lo sugirió @Daniel Burke en la respuesta actualmente aceptada, pero su respuesta es más sucinta. En Python 3.5+ incluso podrías hacer dict(**{'x': 1}, y=2, **{'z': 3}). Ver pep 448 .
jfs
1
Esta respuesta explica algunas formas mejores (y por qué esta forma no es tan buena) para combinar dos diccionarios en uno nuevo: stackoverflow.com/a/26853961/27729
krupan
@krupan: ¿qué desventaja ves para este caso de uso específico ? (fusionar dictados arbitrarios y copiar / actualizar el entorno son tareas diferentes).
jfs
1
@krupan En primer lugar, el caso normal es que las variables de entorno serían identificadores válidos de Python, lo que significa la primera construcción. Para ese caso, ninguna de sus objeciones se cumple. Para el segundo caso, su objeción principal sigue fallando: el punto sobre las claves que no son cadenas no es aplicable en este caso, ya que las claves son básicamente cadenas en el entorno de todos modos.
Skyking
@JFSebastian Tienes razón en que para este caso específico esta técnica está bien y debería haberme explicado mejor. Mis disculpas. Solo quería ayudar a aquellos (como yo) que podrían haber tenido la tentación de tomar esta técnica y aplicarla al caso general de fusionar dos diccionarios arbitrarios (que tiene algunos trucos, como señaló la respuesta a la que me vinculé).
Krupan
24

puede usar en my_env.get("PATH", '')lugar de my_env["PATH"]en caso de que de PATHalguna manera no esté definido en el entorno original, pero aparte de eso, se ve bien.

SilentGhost
fuente
20

Con Python 3.5 puedes hacerlo de esta manera:

import os
import subprocess

my_env = {**os.environ, 'PATH': '/usr/sbin:/sbin:' + os.environ['PATH']}

subprocess.Popen(my_command, env=my_env)

Aquí terminamos con una copia os.environy un PATHvalor anulado .

Fue posible gracias a PEP 448 (Generalizaciones adicionales de desempaque).

Otro ejemplo. Si tiene un entorno predeterminado (es decir os.environ) y un dict con el que desea anular los valores predeterminados, puede expresarlo así:

my_env = {**os.environ, **dict_with_env_variables}
skovorodkin
fuente
@avinash, consulte el subproceso . Documentación abierta . Es "una secuencia de argumentos del programa o una sola cadena".
skovorodkin
10

Para establecer temporalmente una variable de entorno sin tener que copiar el objeto os.envrion, etc., hago esto:

process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://[email protected]::'], stdout=subprocess.PIPE)
MFB
fuente
4

El parámetro env acepta un diccionario. Simplemente puede tomar os.environ, agregar una clave (su variable deseada) (a una copia del dict si es necesario) y usarla como parámetro para Popen.

Noufal Ibrahim
fuente
Esta es la respuesta más simple si solo desea agregar una nueva variable de entorno. os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley
1

Sé que esto ha sido respondido por algún tiempo, pero hay algunos puntos que algunos pueden desear saber sobre el uso de PYTHONPATH en lugar de PATH en su variable de entorno. He esbozado una explicación de cómo ejecutar scripts de Python con cronjobs que trata el entorno modificado de una manera diferente (que se encuentra aquí ). Pensé que sería de algo bueno para aquellos que, como yo, necesitaban un poco más de lo que esta respuesta proporcionó.

derigible
fuente
0

En ciertas circunstancias, es posible que solo desee transmitir las variables de entorno que su subproceso necesita, pero creo que tiene la idea correcta en general (así es como yo también lo hago).

Andrew Aylett
fuente