Python-Unterprozess / Popen mit einer geänderten Umgebung

284

Ich glaube, dass das Ausführen eines externen Befehls in einer leicht geänderten Umgebung ein sehr häufiger Fall ist. So mache ich das:

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

Ich habe das Gefühl, dass es einen besseren Weg gibt. sieht es gut aus

Oren_H
quelle
10
Verwenden Sie os.pathsepanstelle von ":" auch Pfade, die plattformübergreifend funktionieren. Siehe stackoverflow.com/questions/1499019/…
amit
8
@phaedrus Ich bin nicht sicher, ob es sehr relevant ist, wenn er Pfade verwendet wie /usr/sbin:-)
Dmitry Ginzburg

Antworten:

403

Ich denke, es os.environ.copy()ist besser, wenn Sie nicht beabsichtigen, die os.environ für den aktuellen Prozess zu ändern:

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
quelle
>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (letzter Aufruf zuletzt): Datei "<stdin>", Zeile 1, in <module> TypeError: 'instancemethod' Objekt unterstützt keine
Artikelzuweisung
5
@ user1338062 Sie sind die eigentliche Methode Zuordnung os.environ.copyzu der envVariable , aber Sie müssen das Ergebnis der Aufruf der Methode zuweisen os.environ.copy()zu env.
Chown
4
Die Auflösung der Umgebungsvariablen funktioniert nur, wenn Sie sie shell=Truein Ihrem subprocess.PopenAufruf verwenden. Beachten Sie, dass dies möglicherweise Auswirkungen auf die Sicherheit hat.
Danielpops
Innerhalb von subprocess.Popen (my_command, env = my_env) - was ist "my_command"
avinash
@avinash - my_commandist nur ein Befehl zum Ausführen. Dies kann beispielsweise /path/to/your/own/programeine andere "ausführbare" Anweisung sein.
kajakIYD
64

Das hängt davon ab, worum es geht. Wenn die Umgebung geklont und geändert werden soll, könnte eine Lösung sein:

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

Dies hängt jedoch etwas davon ab, dass die ersetzten Variablen gültige Python-Bezeichner sind, die sie am häufigsten sind (wie oft stoßen Sie auf Umgebungsvariablennamen, die nicht alphanumerisch + unterstrichen sind, oder auf Variablen, die mit einer Zahl beginnen?).

Andernfalls könnten Sie etwas schreiben wie:

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

In dem sehr seltsamen Fall (wie oft verwenden Sie Steuercodes oder Nicht-ASCII-Zeichen in Umgebungsvariablennamen?), Dass die Schlüssel der Umgebung sind bytes, können Sie (auf Python3) dieses Konstrukt nicht einmal verwenden.

Wie Sie sehen können, sind die Techniken (insbesondere die erste), die hier für die Schlüssel der Umgebung verwendet werden, normalerweise gültige Python-Kennungen, und auch im Voraus (zum Codierungszeitpunkt) bekannt. Der zweite Ansatz weist Probleme auf. In Fällen, in denen dies nicht der Fall ist, sollten Sie wahrscheinlich nach einem anderen Ansatz suchen .

Himmel König
quelle
3
upvote. Ich wusste nicht, dass du schreiben kannst dict(mapping, **kwargs). Ich dachte es wäre entweder oder. Hinweis: Es wird kopiert, os.environohne es zu ändern, wie es @Daniel Burke in der aktuell akzeptierten Antwort vorgeschlagen hat, aber Ihre Antwort ist prägnanter. In Python 3.5+ können Sie dies sogar tun dict(**{'x': 1}, y=2, **{'z': 3}). Siehe S. 448 .
JFS
1
Diese Antwort erklärt einige bessere Möglichkeiten (und warum dieser Weg nicht so toll ist), zwei Wörterbücher zu einem neuen zusammenzuführen: stackoverflow.com/a/26853961/27729
krupan
@krupan: Welchen Nachteil sehen Sie für diesen speziellen Anwendungsfall? (Das Zusammenführen beliebiger Diktate und das Kopieren / Aktualisieren der Umgebung sind verschiedene Aufgaben).
JFS
1
@krupan Zunächst einmal ist der Normalfall, dass Umgebungsvariablen gültige Python-Bezeichner sind, was das erste Konstrukt bedeutet. Für diesen Fall gilt keiner Ihrer Einwände. Für den zweiten Fall schlägt Ihr Hauptgrund immer noch fehl: Der Punkt über Nicht-String-Schlüssel ist in diesem Fall nicht anwendbar, da die Schlüssel grundsätzlich ohnehin Strings in der Umgebung sein müssen.
Skyking
@JFSebastian Sie haben Recht, dass diese Technik für diesen speziellen Fall in Ordnung ist und ich hätte mich besser erklären sollen. Entschuldigen Sie. Ich wollte nur denen helfen (wie mir selbst), die möglicherweise versucht waren, diese Technik anzuwenden und sie auf den allgemeinen Fall des Zusammenführens zweier beliebiger Wörterbücher anzuwenden (der einige Gotchas enthält, wie die Antwort, auf die ich verwiesen habe, hervorhob).
Krupan
24

Sie können my_env.get("PATH", '')anstelle von my_env["PATH"]für den Fall verwenden PATH, dass es in der ursprünglichen Umgebung nicht definiert ist, aber ansonsten sieht es gut aus.

SilentGhost
quelle
20

Mit Python 3.5 können Sie dies folgendermaßen tun:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Hier erhalten wir eine Kopie des os.environüberschriebenen PATHWerts.

Möglich wurde dies durch PEP 448 (Additional Unpacking Generalizations).

Ein anderes Beispiel. Wenn Sie eine Standardumgebung (dh os.environ) und ein Diktat haben, mit dem Sie die Standardeinstellungen überschreiben möchten, können Sie dies folgendermaßen ausdrücken:

my_env = {**os.environ, **dict_with_env_variables}
Skovorodkin
quelle
@avinash Besuche subprocess.Popen Dokumentation. Es ist "eine Folge von Programmargumenten oder eine einzelne Zeichenfolge".
Skovorodkin
10

Um eine Umgebungsvariable vorübergehend festzulegen, ohne das Objekt os.envrion usw. kopieren zu müssen, gehe ich folgendermaßen vor:

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

Der Parameter env akzeptiert ein Wörterbuch. Sie können einfach os.environ nehmen, einen Schlüssel (Ihre gewünschte Variable) (zu einer Kopie des Diktats, wenn Sie müssen) hinzufügen und ihn als Parameter für verwenden Popen.

Noufal Ibrahim
quelle
Dies ist die einfachste Antwort, wenn Sie nur eine neue Umgebungsvariable hinzufügen möchten. os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley
1

Ich weiß, dass dies seit einiger Zeit beantwortet wurde, aber es gibt einige Punkte, die einige über die Verwendung von PYTHONPATH anstelle von PATH in ihrer Umgebungsvariablen wissen möchten. Ich habe eine Erklärung zum Ausführen von Python-Skripten mit Cronjobs skizziert, die die geänderte Umgebung auf andere Weise behandelt ( hier zu finden ). Ich dachte, es wäre etwas Gutes für diejenigen, die wie ich nur ein bisschen mehr brauchten als diese Antwort.

abzugsfähig
quelle
0

Unter bestimmten Umständen möchten Sie möglicherweise nur die Umgebungsvariablen weitergeben, die Ihr Unterprozess benötigt, aber ich denke, Sie haben im Allgemeinen die richtige Idee (so mache ich das auch).

Andrew Aylett
quelle