変更された環境でのPythonサブプロセス/ Popen


284

少し変更された環境で外部コマンドを実行することは非常に一般的なケースだと思います。それは私がそれをする傾向がある方法です:

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

私にはもっと良い方法があると直感しています。大丈夫ですか?


10
また、os.pathsepプラットフォーム間で機能するパスには、「:」の代わりに使用することをお勧めします。stackoverflow.com/questions/1499019/…を
amit

8
@phaedrus彼が/usr/sbin:-)のようなパスを使用しているとき、それが非常に関連があるとは思えません
Dmitry Ginzburg

回答:


403

os.environ.copy()現在のプロセスのos.environを変更するつもりがない場合は、より良いと思います。

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'トレースバック(最新の最後の呼び出し):ファイル "<stdin>"、1行目<module> TypeError: 'instancemethod'オブジェクトはアイテムの割り当てをサポートしていません
user1338062

5
@ user1338062あなたは、実際のメソッドを割り当てるos.environ.copyenv変数がありますが、メソッドを呼び出した結果割り当てる必要がありますos.environ.copy()にしますenv
チャウン

4
環境変数の解決shell=Trueは、subprocess.Popen呼び出しで使用する場合にのみ実際に機能します。これを行うと、セキュリティに影響する可能性があることに注意してください。
danielpops

サブプロセス内.Popen(my_command、env = my_env)-「my_command」とは
avinash

@avinash- my_command実行するコマンドです。これは、たとえば、インスタンス/path/to/your/own/programまたはその他の「実行可能な」ステートメントである可能性があります。
kajakIYD

64

それは問題が何であるかに依存します。環境を複製して変更する場合、1つの解決策は次のようになります。

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

しかし、それは、置き換えられた変数が有効なpython識別子であることによって多少異なりますが、それらはほとんどの場合そうです(英数字+アンダースコアではない環境変数名、または数値で始まる変数にどれくらいの頻度で遭遇しますか?)。

それ以外の場合は、次のように記述できます。

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

非常に奇妙なケース(環境変数名で制御コードまたは非ASCII文字をどのくらいの頻度で使用しますか?)では、環境のキーは次のとおりです。 bytes(python3では)その構造を使用することさえできません。

ここで使用されている手法(特に最初の手法)が環境のキーにメリットをもたらすことがわかるように、通常は有効なpython識別子であり、事前に(コーディング時に)わかっているため、2番目の手法には問題があります。そうでない場合は、おそらく別のアプローチを探す必要があります。


3
賛成票。私はあなたが書くことができることを知りませんでしたdict(mapping, **kwargs)。かと思った。注:現在受け入れられている回答で@Daniel Burkeが提案したように、os.environ変更せずにコピーしますが、あなたの回答はより簡潔です。Python 3.5以降では、これも可能ですdict(**{'x': 1}, y=2, **{'z': 3})pep 448を参照してください。
jfs 2015年

1
この答えは、いくつかのより良い方法を説明します(この方法はそれほど大きくない理由)1に新しいものを2つの辞書をマージする: stackoverflow.com/a/26853961/27729
krupan

@krupan:この特定のユースケースにはどのような欠点がありますか?(任意の辞書のマージと環境のコピー/更新は別のタスクです)。
jfs

1
@krupanまず最初に、通常の場合は、環境変数が有効なpython識別子になることです。これは、最初の構成を意味します。その場合、あなたの異論はどれも成立しません。2番目のケースでは、主な反対意見は依然として失敗します。文字列以外のキーに関するポイントは、キーは基本的に環境では文字列である必要があるため、この場合は適用されません。
2016年

@JFSebastianこの特定のケースでは、この手法は適切であり、私は自分自身をよりよく説明するべきでした。謝罪いたします。私は、このテクニックを取り入れて、2つの任意の辞書をマージする一般的なケース(いくつかの落とし穴があります、私が指摘した答えが指摘しているように)に適用したいと思った人々(私など)を助けたかっただけです。
クルパン、2016年

24

元の環境でどういうわけか定義されていない場合のmy_env.get("PATH", '')代わりに使用できますが、それ以外は問題なく見えます。my_env["PATH"]PATH


20

Python 3.5では、次のようにすることができます。

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

ここでos.environPATH値のコピーと上書きされた結果になります。

PEP 448(Additional Unpacking Generalizations)によって可能になりました。

もう一つの例。デフォルト環境(つまりos.environ)があり、デフォルトを上書きするディクテーションがある場合、次のように表現できます。

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

@ avinash、subprocess.Popenのドキュメントを確認してください。それは「プログラムの引数のシーケンスか、そうでなければ単一の文字列」です。
skovorodkin

10

os.envrionオブジェクトなどをコピーせずに一時的に環境変数を設定するには、次のようにします。

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

4

envパラメータは辞書を受け入れます。単にos.environを取得し、それにキー(必要な変数)を(必要な場合はdictのコピーに)追加し、それをのパラメーターとして使用できますPopen


これは、新しい環境変数を追加したいだけの場合の最も簡単な答えです。 os.environ['SOMEVAR'] = 'SOMEVAL'
アンディフレーリー

1

私はこれがしばらくの間回答されていることを知っていますが、環境変数でPATHの代わりにPYTHONPATHを使用することについて知りたいと思う点がいくつかあります。変更された環境を別の方法で処理するcronjobsでpythonスクリプトを実行する方法の説明を概説しましたここにあります)。私のように、この回答が提供するよりも少しだけ多くを必要とする人にとっては、それは良いことだと思いました。


0

特定の状況では、サブプロセスが必要とする環境変数のみを渡したい場合がありますが、一般的には正しい考えを持っていると思います(それが私もそうです)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.