Эмуляция Bash 'source' в Python

У меня есть script, который выглядит примерно так:

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

Каждый раз, когда я создаю, я запускаю "source init_env" (где init_env - это выше script), чтобы установить некоторые переменные.

Чтобы выполнить то же самое в 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 script развалился. Я мог бы изменить Python script, чтобы обработать эту строку, но потом он просто сломается позже, когда кто-то придумает новую функцию, которая будет использоваться в файле init_env.

Вопрос в том, есть ли простой способ запустить команду Bash и изменить ее os.environ?

Ответ 1

Проблема с вашим подходом заключается в том, что вы пытаетесь интерпретировать сценарии bash. Сначала вы просто попытаетесь интерпретировать экспортную инструкцию. Затем вы замечаете, что люди используют переменное расширение. Позже люди помещают условные обозначения в свои файлы или замену процессов. В итоге у вас будет полнофункциональный интерпретатор bash script с gazillion ошибками. Не делайте этого.

Пусть bash интерпретирует файл для вас, а затем собирает результаты.

Вы можете сделать это следующим образом:

#! /usr/bin/env python

import os
import pprint
import subprocess

command = ['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 или любые другие ошибки.

Подробнее см. документацию на subprocess.

Примечание. Это приведет только к захвату переменных, заданных с помощью оператора export, поскольку env печатает только экспортированные переменные.

Enjoy.

Обратите внимание, что документация на Python говорит, что если вы хотите управлять средой, вы должны манипулировать os.environ напрямую, а не использовать os.putenv(). Я считаю, что ошибка, но я отвлекаюсь.

Ответ 2

Использование рассола:

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, subprocess и явно использует /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

Ответ 3

Вместо источника Python script bash script было бы проще и элегантнее иметь оболочку script source init_env, а затем запустить Python script с измененной средой.

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

Ответ 4

Обновлен ответ @lesmana для Python 3. Обратите внимание на использование env -i, которое предотвращает установку внешних переменных среды /reset (потенциально неправильно учитывая отсутствие обработки для многострочных переменных env).

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

Ответ 5

Пример wrapping @Brian отличный ответ в функции:

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.

Ответ 6

execfile('filename')

должен сделать это