Subprocesso Python / Popen com um ambiente modificado

284

Acredito que executar um comando externo com um ambiente ligeiramente modificado é um caso muito comum. É assim que eu faço:

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

Tenho a sensação de que há uma maneira melhor; parece bem?

Oren_H
fonte
10
Também prefira usar em os.pathsepvez de ":" para caminhos que funcionam entre plataformas. Veja stackoverflow.com/questions/1499019/…
amit
8
@phaedrus eu não tenho certeza que é muito relevante quando ele está usando caminhos como /usr/sbin:-)
Dmitry Ginzburg

Respostas:

403

Eu acho que os.environ.copy()é melhor se você não pretende modificar o os.environ para o processo atual:

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
fonte
>>> env = os.environ.copy >>> env ['foo'] = 'bar' Traceback (última chamada mais recente): Arquivo "<stdin>", linha 1, em <module> TypeError: 'instancemethod' objeto não suporta atribuição artigo
user1338062
5
@ user1338062 Você está atribuindo o método real os.environ.copypara a envvariável, mas você precisa para atribuir o resultado de chamar o método os.environ.copy()a env.
chown
4
A resolução da variável de ambiente realmente funciona apenas se você usar shell=Truesua subprocess.Popenchamada. Observe que existem potencialmente implicações de segurança para fazer isso.
Danielpops 30/05
Dentro subprocess.Popen (my_command, env = my_env) - o que é "my_command"
avinash
@avinash - my_commandé apenas um comando para executar. Pode ser, por exemplo, /path/to/your/own/programou qualquer outra declaração "executável".
kajakIYD
64

Isso depende de qual é o problema. Se for para clonar e modificar o ambiente, uma solução pode ser:

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

Mas isso depende um pouco do fato de as variáveis ​​substituídas serem identificadores python válidos, o que geralmente é (com que frequência você encontra nomes de variáveis ​​de ambiente que não são alfanuméricos + sublinhado ou variáveis ​​que começam com um número?).

Caso contrário, você poderá escrever algo como:

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

No caso muito estranho (com que frequência você usa códigos de controle ou caracteres não-ascii em nomes de variáveis ​​de ambiente?) Que as chaves do ambiente são as que bytesvocê não pode (no python3) usar essa construção.

Como você pode ver as técnicas (especialmente a primeira) usadas aqui, os benefícios nas chaves do ambiente são normalmente identificadores python válidos e também conhecidos antecipadamente (no momento da codificação), a segunda abordagem tem problemas. Nos casos em que esse não é o caso, você provavelmente deve procurar outra abordagem .

skyking
fonte
3
voto positivo. Eu não sabia que você poderia escrever dict(mapping, **kwargs). Eu pensei que era ou. Nota: ele é copiado os.environsem modificá-lo, conforme sugerido por Daniel Burke na resposta atualmente aceita, mas sua resposta é mais sucinta. No Python 3.5+ você pode até fazer dict(**{'x': 1}, y=2, **{'z': 3}). Veja pep 448 .
JFS
1
Esta resposta explica algumas maneiras melhores (e porque desta forma não é tão grande) para mesclar dois dicionários em um novo: stackoverflow.com/a/26853961/27729
krupan
@krupan: que desvantagem você vê para este caso de uso específico ? (mesclar regras arbitrárias e copiar / atualizar o ambiente são tarefas diferentes).
JFS
1
@krupan Primeiro de tudo, o caso normal é que as variáveis ​​de ambiente seriam identificadores python válidos, o que significa a primeira construção. Nesse caso, nenhuma de suas objeções se mantém. Para o segundo caso, sua principal objeção ainda falha: o argumento sobre chaves que não são de cadeia não é aplicável neste caso, pois as chaves precisam basicamente ser cadeias de caracteres no ambiente.
skyking
@JFSebastian Você está certo de que, para este caso específico, esta técnica é adequada e eu deveria ter me explicado melhor. Me desculpe. Eu só queria ajudar aqueles (como eu) que poderiam ter sido tentados a usar essa técnica e aplicá-la ao caso geral de mesclar dois dicionários arbitrários (que têm algumas pegadinhas, como a resposta que eu indiquei).
Kdran
24

você pode usar em my_env.get("PATH", '')vez de my_env["PATH"], caso PATHnão esteja definido de alguma forma no ambiente original, mas que não seja o que parece ser bom.

SilentGhost
fonte
20

Com o Python 3.5, você pode fazer o seguinte:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Aqui terminamos com uma cópia os.environe um PATHvalor substituído .

Foi possível pelo PEP 448 (Generalizações adicionais de desempacotamento).

Outro exemplo. Se você possui um ambiente padrão (por exemplo os.environ) e um ditado com o qual deseja substituir os padrões, você pode expressá-lo assim:

my_env = {**os.environ, **dict_with_env_variables}
skovorodkin
fonte
@avinash, consulte a documentação do subprocesso . É "uma sequência de argumentos do programa ou então uma única sequência".
Skovorodkin 21/08/19
10

Para definir temporariamente uma variável de ambiente sem precisar copiar o objeto os.envrion, etc, faço isso:

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

O parâmetro env aceita um dicionário. Você pode simplesmente pegar o os.environ, adicionar uma chave (sua variável desejada) (a uma cópia do ditado, se necessário) e usar como parâmetro para Popen.

Noufal Ibrahim
fonte
Essa é a resposta mais simples se você quiser apenas adicionar uma nova variável de ambiente. os.environ['SOMEVAR'] = 'SOMEVAL'
precisa
1

Sei que isso já foi respondido há algum tempo, mas há alguns pontos que alguns podem querer saber sobre o uso de PYTHONPATH em vez de PATH em sua variável de ambiente. Descrevi uma explicação da execução de scripts python com cronjobs que lida com o ambiente modificado de uma maneira diferente ( encontrada aqui ). Achei que seria de alguma utilidade para quem, como eu, precisava de um pouco mais do que a resposta fornecida.

derigible
fonte
0

Em certas circunstâncias, você pode querer passar apenas as variáveis ​​de ambiente de que seu subprocesso precisa, mas acho que você teve a idéia certa em geral (é assim que eu também faço).

Andrew Aylett
fonte