Python sottoprocesso / Popen con un ambiente modificato


285

Credo che eseguire un comando esterno con un ambiente leggermente modificato sia un caso molto comune. È così che tendo a farlo:

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

Ho la sensazione che ci sia un modo migliore; sembra a posto?


10
Inoltre, preferisci utilizzare os.pathsepinvece di ":" per i percorsi che funzionano su più piattaforme. Vedere stackoverflow.com/questions/1499019/...
Amit

8
@phaedrus Non sono sicuro che sia molto rilevante quando usa percorsi come /usr/sbin:-)
Dmitry Ginzburg

Risposte:


405

Penso che os.environ.copy()sia meglio se non si intende modificare os.environ per il processo corrente:

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

>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (ultima chiamata più recente): File "<stdin>", riga 1, in <module> TypeError: 'instancemethod' L'oggetto non supporta l'assegnazione degli elementi
user1338062

5
@ user1338062 si sta assegnando il metodo effettivo os.environ.copyalla envvariabile, ma è necessario assegnare il risultato della chiamata al metodo os.environ.copy()a env.
vestito il

4
La risoluzione della variabile di ambiente funziona effettivamente solo se si utilizza shell=Truenella propria subprocess.Popenchiamata. Si noti che ci sono potenzialmente implicazioni di sicurezza nel farlo.
danielpops,

Inside subprocess.Popen (my_command, env = my_env) - che cos'è "my_command"
avinash

@avinash: my_commandè solo un comando da eseguire. Potrebbe essere ad esempio /path/to/your/own/programo qualsiasi altra istruzione "eseguibile".
kajakIYD

64

Dipende da quale sia il problema. Se si tratta di clonare e modificare l'ambiente, una soluzione potrebbe essere:

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

Ma ciò dipende in qualche modo dal fatto che le variabili sostituite sono identificatori python validi, cosa che spesso accade (con quale frequenza si imbattono in nomi di variabili di ambiente che non sono alfanumerici + carattere di sottolineatura o variabili che iniziano con un numero?).

Altrimenti potresti scrivere qualcosa del tipo:

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

Nel caso molto strano (con quale frequenza usi codici di controllo o caratteri non ascii nei nomi delle variabili di ambiente?) Che le chiavi dell'ambiente bytesnon puoi (su python3) nemmeno usare quel costrutto.

Come puoi vedere, le tecniche (specialmente la prima) qui usate benefici sulle chiavi dell'ambiente normalmente sono validi identificatori python, e anche noti in anticipo (al momento della codifica), il secondo approccio ha dei problemi. Nei casi in cui non è così, dovresti probabilmente cercare un altro approccio .


3
upvote. Non sapevo che tu potessi scrivere dict(mapping, **kwargs). Ho pensato che fosse o. Nota: copia os.environsenza modificarlo come suggerito da @Daniel Burke nella risposta attualmente accettata ma la tua risposta è più concisa. In Python 3.5+ potresti persino fare dict(**{'x': 1}, y=2, **{'z': 3}). Vedi pep 448 .
jfs

1
Questa risposta spiega alcuni modi migliori (e perché in questo modo non è così grande) per unire due dizionari in uno nuovo: stackoverflow.com/a/26853961/27729
krupan

@krupan: quale svantaggio vedi per questo specifico caso d'uso? (la fusione di dicti arbitrari e la copia / aggiornamento dell'ambiente sono compiti diversi).
jfs,

1
@krupan Innanzitutto il caso normale è che le variabili di ambiente sarebbero identificatori python validi, il che significa che il primo costrutto. In tal caso nessuna delle tue obiezioni è valida. Per il secondo caso, la tua obiezione principale non riesce ancora: il punto sulle chiavi non stringa non è applicabile in questo caso poiché le chiavi sono fondamentalmente richieste per essere stringhe nell'ambiente comunque.
skyking

@JFSebastian Hai ragione sul fatto che per questo caso specifico questa tecnica va bene e avrei dovuto spiegarmi meglio. Mie scuse. Volevo solo aiutare quelli (come me) che potrebbero essere stati tentati di prendere questa tecnica e applicarla al caso generale di fusione di due dizionari arbitrari (che ha alcuni gotcha, come indicato dalla risposta che ho indicato).
Krupan,

24

potresti usare my_env.get("PATH", '')invece my_env["PATH"]nel caso in PATHqualche modo non definito nell'ambiente originale, ma a parte questo sembra a posto.


21

Con Python 3.5 puoi farlo in questo modo:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Qui finiamo con una copia di os.environe PATHvalore sovrascritto .

È stato reso possibile da PEP 448 (Generalizzazioni di disimballaggio aggiuntive).

Un altro esempio. Se si dispone di un ambiente predefinito (ad es. os.environ) E un dict con cui si desidera sostituire le impostazioni predefinite, è possibile esprimerlo in questo modo:

my_env = {**os.environ, **dict_with_env_variables}

@avinash, controlla la documentazione di sottoprocesso . È "una sequenza di argomenti del programma oppure una singola stringa".
skovorodkin,

10

Per impostare temporaneamente una variabile di ambiente senza dover copiare l'oggetto os.envrion ecc., Faccio questo:

process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://username@foobar.com::'], stdout=subprocess.PIPE)

4

Il parametro env accetta un dizionario. Puoi semplicemente prendere os.environ, aggiungere una chiave (la tua variabile desiderata) (a una copia del dict se necessario) e usarla come parametro per Popen.


Questa è la risposta più semplice se si desidera solo aggiungere una nuova variabile di ambiente. os.environ['SOMEVAR'] = 'SOMEVAL'
Andy Fraley,

1

So che è stato risposto per un po 'di tempo, ma ci sono alcuni punti che alcuni potrebbero voler sapere sull'uso di PYTHONPATH invece di PATH nella loro variabile di ambiente. Ho delineato una spiegazione dell'esecuzione di script Python con cronjobs che si occupa dell'ambiente modificato in un modo diverso ( trovato qui ). Ho pensato che sarebbe stato utile per coloro che, come me, avevano bisogno di qualcosa in più di questa risposta fornita.


0

In alcune circostanze potresti voler tramandare solo le variabili di ambiente di cui il tuo sottoprocesso ha bisogno, ma penso che tu abbia la giusta idea in generale (è così che lo faccio anche io).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.