PythonでBashの「ソース」をエミュレートする


91

次のようなスクリプトがあります。

export foo=/tmp/foo                                          
export bar=/tmp/bar

ビルドするたびに、 'source init_env'(ここでinit_envは上記のスクリプトです)を実行して、いくつかの変数を設定します。

Pythonで同じことを行うために、このコードを実行しました。

reg = re.compile('export (?P<name>\w+)(\=(?P<value>.+))*')
for line in open(file):
    m = reg.match(line)
    if m:
        name = m.group('name')
        value = ''
        if m.group('value'):
            value = m.group('value')
        os.putenv(name, value)

しかし、誰かが次のような行をinit_envファイルに追加すると便利だと判断しました。

export PATH="/foo/bar:/bar/foo:$PATH"     

明らかに私のPythonスクリプトはバラバラになりました。この行を処理するようにPythonスクリプトを変更することもできますが、後で誰かinit_envファイルで使用する新しい機能を思いついたときに中断します。

問題は、Bashコマンドを実行してそれを変更する簡単な方法があるos.environかどうかです。


回答:


109

あなたのアプローチの問題は、bashスクリプトを解釈しようとしていることです。まず、exportステートメントを解釈してみます。次に、人々が変数展開を使用していることに気づきます。後で人々は彼らのファイルに条件文を置くか、代用を処理します。結局、あなたはガジリオンのバグを含んだ本格的なbashスクリプトインタプリタを手にするでしょう。それをしないでください。

Bashにファイルを解釈させ、結果を収集させます。

あなたはこのようにそれを行うことができます:

#! /usr/bin/env python

import os
import pprint
import shlex
import subprocess

command = shlex.split("env -i bash -c 'source init_env && env'")
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
for line in proc.stdout:
  (key, _, value) = line.partition("=")
  os.environ[key] = value
proc.communicate()

pprint.pprint(dict(os.environ))

bashがに失敗した場合source init_env、bash自体の実行に失敗した場合、サブプロセスがbashの実行に失敗した場合、またはその他のエラーが発生した場合は、必ずエラーを処理してください。

env -iコマンドラインの先頭には、クリーンな環境を作成します。つまり、環境変数のみを取得しますinit_env。継承されたシステム環境が必要な場合は省略しenv -iます。

詳細については、サブプロセスに関するドキュメントをご覧ください。

注:これはexportenvエクスポートされた変数のみを出力するため、ステートメントで設定された変数のみをキャプチャします。

楽しい。

ことに注意してくださいPythonドキュメントは、あなたが環境を操作したい場合は、操作する必要があることを述べているos.environ使用するのではなく直接os.putenv()。私はそれをバグと考えていますが、余談です。


12
エクスポートされていない変数に注意し、スクリプトが制御外にある場合は、set -aを使用して、すべての変数をエクスポート済みとしてマークできます。コマンドを次のように変更してください:['bash'、 '-c'、 'set -a && source init_env && env']
ahal

これは、エクスポートされた関数では失敗することに注意してください。関数に対しても機能する構文解析を示す回答を更新できたら、私はそれを気に入っています。(eg function fff(){echo "fff";}; export -f fff)
DA

2
注:これは複数行の環境変数をサポートしていません。
BenC 2017年

2
私の場合、繰り返し処理するproc.stdout()とバイトが生成されるため、TypeErrorを取得していましたline.partition()line.decode().partition("=")問題を解決した文字列に変換します。
サムF

1
これはとても役に立ちました。['env', '-i', 'bash', '-c', 'source .bashrc && env']rcファイルで設定された環境変数のみを使用するように実行しました
xaviersjs

32

ピクルスを使う:

import os, pickle
# For clarity, I moved this string out of the command
source = 'source init_env'
dump = '/usr/bin/python -c "import os,pickle;print pickle.dumps(os.environ)"'
penv = os.popen('%s && %s' %(source,dump))
env = pickle.loads(penv.read())
os.environ = env

更新しました:

これはjson、サブプロセスを使用し、明示的に/ bin / bashを使用します(ubuntuサポート用)。

import os, subprocess as sp, json
source = 'source init_env'
dump = '/usr/bin/python -c "import os, json;print json.dumps(dict(os.environ))"'
pipe = sp.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=sp.PIPE)
env = json.loads(pipe.stdout.read())
os.environ = env

これはUbuntuで問題があります-デフォルトのシェルがあり/bin/dashsourceコマンドを認識しません。Ubuntuで使用するには、/bin/bash明示的に実行する必要があります。たとえば、次のように使用しますpenv = subprocess.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=subprocess.PIPE).stdoutsubprocessインポートする必要がある新しいモジュールを使用します)。
Martin Pecka

22

Pythonスクリプトのソースにbashスクリプトを用意するのではなく、ラッパースクリプトのソースinit_envを用意して、変更した環境でPythonスクリプトを実行する方が簡単でエレガントです。

#!/bin/bash
source init_env
/run/python/script.py

4
状況によっては問題が解決する場合がありますが、すべてではありません。たとえば、ファイルのソースのような何かをする必要があるpythonスクリプトを書いていて(私が話していることがわかっている場合は、実際にはモジュールをロードします)、状況によっては別のモジュールをロードする必要があります。だから、これは私の問題をまったく解決しません
Davide

これはほとんどの場合の質問に対する答えです。可能な限り使用します。特定のプロジェクトのIDEでこれを機能させるのに苦労しました。一つの可能な変更が環境とシェルで全部を実行するかもしれないbash --rcfile init_env -c ./script.py
xaviersjs

6

Python 3に対する@lesmanaの回答を更新しました。env -iこれを使用すると、無関係な環境変数が設定/リセットされるのを防ぐことができます(複数行の環境変数を処理できないため、誤って使用される可能性があります)。

import os, subprocess
if os.path.isfile("init_env"):
    command = 'env -i sh -c "source init_env && env"'
    for line in subprocess.getoutput(command).split("\n"):
        key, value = line.split("=")
        os.environ[key]= value

env -iはパスの設定を解除するため、これを使用すると「PATH:undefined variable」が表示されます。ただし、env -iがなくても機能します。また、行に複数の「=」が含まれる可能性があることに注意してください
Fujii

4

@ブライアンの優れた答えを関数にラップする例:

import json
import subprocess

# returns a dictionary of the environment variables resulting from sourcing a file
def env_from_sourcing(file_to_source_path, include_unexported_variables=False):
    source = '%ssource %s' % ("set -a && " if include_unexported_variables else "", file_to_source_path)
    dump = '/usr/bin/python -c "import os, json; print json.dumps(dict(os.environ))"'
    pipe = subprocess.Popen(['/bin/bash', '-c', '%s && %s' % (source, dump)], stdout=subprocess.PIPE)
    return json.loads(pipe.stdout.read())

このユーティリティ関数を使用して、awsの資格情報とdocker .envファイルをで読み取りますinclude_unexported_variables=True

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