Sous-processus Python / Popen avec un environnement modifié

285

Je crois que l'exécution d'une commande externe avec un environnement légèrement modifié est un cas très courant. Voilà comment j'ai tendance à le faire:

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

J'ai l'intuition qu'il y a une meilleure façon; ça a l'air bien?

Oren_H
la source
10
Préférez également utiliser os.pathsepau lieu de ":" pour les chemins d'accès qui fonctionnent sur plusieurs plateformes. Voir stackoverflow.com/questions/1499019/...
Amit
8
@phaedrus Je ne suis pas sûr que ce soit très pertinent quand il utilise des chemins comme /usr/sbin:-)
Dmitry Ginzburg

Réponses:

406

Je pense que os.environ.copy()c'est mieux si vous n'avez pas l'intention de modifier os.environ pour le processus actuel:

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
la source
>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (dernier appel le plus récent): Fichier "<stdin>", ligne 1, dans <module> TypeError: 'instancemethod' l'objet ne prend pas en charge l'attribution d'élément
user1338062
5
@ user1338062 Vous affectez la méthode réelle os.environ.copyà la envvariable mais vous devez affecter le résultat de l'appel de la méthode os.environ.copy()à env.
chown
4
La résolution de la variable d'environnement ne fonctionne réellement que si vous l'utilisez shell=Truedans votre subprocess.Popenappel. Notez que cela peut potentiellement impliquer des problèmes de sécurité.
danielpops
Sous-processus interne.Popen (ma_commande, env = mon_env) - qu'est-ce que "ma_commande"
avinash
@avinash - my_commandest juste une commande à exécuter. Il peut s'agir par exemple /path/to/your/own/programou de toute autre instruction "exécutable".
kajakIYD
65

Cela dépend du problème. Pour cloner et modifier l'environnement, une solution pourrait être:

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

Mais cela dépend quelque peu du fait que les variables remplacées sont des identificateurs python valides, ce qu'elles sont le plus souvent (à quelle fréquence rencontrez-vous des noms de variables d'environnement qui ne sont pas alphanumériques + trait de soulignement ou des variables qui commencent par un nombre?).

Sinon, vous pourriez écrire quelque chose comme:

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

Dans le cas très étrange (à quelle fréquence utilisez-vous des codes de contrôle ou des caractères non-ascii dans les noms de variables d'environnement?) Que les clés de l'environnement ne sont bytespas vous (sur python3) même utiliser cette construction.

Comme vous pouvez voir les techniques (en particulier la première) utilisées ici, les avantages sur les clés de l'environnement sont normalement des identifiants python valides, et également connus à l'avance (au moment du codage), la deuxième approche a des problèmes. Dans les cas où ce n'est pas le cas, vous devriez probablement chercher une autre approche .

skyking
la source
4
upvote. Je ne savais pas que tu pouvais écrire dict(mapping, **kwargs). Je pensais que c'était soit ou. Remarque: il copie os.environsans le modifier comme l'a suggéré @Daniel Burke dans la réponse actuellement acceptée, mais votre réponse est plus succincte. En Python 3.5+, vous pourriez même le faire dict(**{'x': 1}, y=2, **{'z': 3}). Voir pep 448 .
jfs
1
Cette réponse explique de meilleures façons (et pourquoi cette méthode n'est pas si bonne) de fusionner deux dictionnaires en un nouveau: stackoverflow.com/a/26853961/27729
krupan
@krupan: quel inconvénient voyez-vous pour ce cas d'utilisation spécifique ? (fusionner des dict arbitraires et copier / mettre à jour l'environnement sont des tâches différentes).
jfs
1
@krupan Tout d'abord, le cas normal est que les variables d'environnement seraient des identifiants python valides, ce qui signifie la première construction. Dans ce cas, aucune de vos objections ne tient. Pour le deuxième cas, votre objection principale échoue toujours: le point sur les clés non-chaîne ne s'applique pas dans ce cas car les clés doivent de toute façon être des chaînes dans l'environnement.
skyking
@JFSebastian Vous avez raison de dire que pour ce cas précis cette technique est très bien et j'aurais dû mieux m'expliquer. Mes excuses. Je voulais juste aider ceux (comme moi) qui auraient pu être tentés de prendre cette technique et de l'appliquer au cas général de la fusion de deux dictionnaires arbitraires (qui a quelques pièges, comme l'a indiqué la réponse que j'ai liée).
krupan
24

vous pouvez utiliser my_env.get("PATH", '')au lieu de my_env["PATH"]dans le cas où PATHnon défini dans l'environnement d'origine, mais à part cela, cela semble bien.

SilentGhost
la source
22

Avec Python 3.5, vous pouvez le faire de cette façon:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Ici, nous nous retrouvons avec une copie de os.environet une PATHvaleur remplacée .

Il a été rendu possible par le PEP 448 (Générations de déballage supplémentaires).

Un autre exemple. Si vous avez un environnement par défaut (c.-à-d. os.environ) Et un dict avec lequel vous souhaitez remplacer les valeurs par défaut, vous pouvez l'exprimer comme ceci:

my_env = {**os.environ, **dict_with_env_variables}
skovorodkin
la source
@avinash, consultez subprocess.Popen documentation. C'est "une séquence d'arguments de programme ou bien une seule chaîne".
skovorodkin
10

Pour définir temporairement une variable d'environnement sans avoir à copier l'objet os.envrion, etc., je fais ceci:

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

Le paramètre env accepte un dictionnaire. Vous pouvez simplement prendre os.environ, ajouter une clé (votre variable souhaitée) (à une copie du dict si vous le devez) et l'utiliser comme paramètre pour Popen.

Noufal Ibrahim
la source
C'est la réponse la plus simple si vous souhaitez simplement ajouter une nouvelle variable d'environnement. os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley
1

Je sais que cela a été répondu depuis un certain temps, mais il y a certains points que certains voudront peut-être savoir sur l'utilisation de PYTHONPATH au lieu de PATH dans leur variable d'environnement. J'ai décrit une explication de l'exécution de scripts python avec cronjobs qui traite l'environnement modifié d'une manière différente ( trouvée ici ). Je pensais que ce serait quelque chose de bon pour ceux qui, comme moi, avaient besoin d'un peu plus que cette réponse.

dérigeable
la source
0

Dans certaines circonstances, vous souhaiterez peut-être transmettre uniquement les variables d'environnement dont votre sous-processus a besoin, mais je pense que vous avez la bonne idée en général (c'est comme ça que je le fais aussi).

Andrew Aylett
la source