Вызов команды «source» из подпроцесса. Открыть - PullRequest
29 голосов
/ 12 августа 2011

У меня есть сценарий .sh, который я вызываю с помощью source the_script.sh.Вызывать это регулярно хорошо.Тем не менее, я пытаюсь вызвать его из моего скрипта Python через subprocess.Popen.

При вызове из Popen я получаю следующие ошибки в следующих двух сценариях вызовов:

foo = subprocess.Popen("source the_script.sh")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 672, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1213, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory


>>> foo = subprocess.Popen("source the_script.sh", shell = True)
>>> /bin/sh: source: not found

Что дает?Почему я не могу назвать «источник» из Popen, когда могу за пределами python?

Ответы [ 7 ]

28 голосов
/ 03 октября 2012

Вы можете просто запустить команду в подоболочке и использовать результаты для обновления текущей среды.

def shell_source(script):
    """Sometime you want to emulate the action of "source" in bash,
    settings some environment variables. Here is a way to do it."""
    import subprocess, os
    pipe = subprocess.Popen(". %s; env" % script, stdout=subprocess.PIPE, shell=True)
    output = pipe.communicate()[0]
    env = dict((line.split("=", 1) for line in output.splitlines()))
    os.environ.update(env)
25 голосов
/ 12 августа 2011

source это не исполняемая команда, это встроенная оболочка.

Самый обычный случай использования source - это запуск сценария оболочки, который изменяет среду и сохраняет эту среду в текущей оболочке. Именно так работает virtualenv для изменения среды Python по умолчанию.

Создание подпроцесса и использование source в подпроцессе, вероятно, не принесет ничего полезного, не изменит среду родительского процесса, не произойдет никаких побочных эффектов использования сценария с источником. .

В Python есть аналогичная команда execfile, которая запускает указанный файл, используя текущее глобальное пространство имен python (или другое, если вы его предоставили), который вы можете использовать аналогично команде bash source.

14 голосов
/ 28 февраля 2014

Broken Popen("source the_script.sh") эквивалентно Popen(["source the_script.sh"]), который безуспешно пытается запустить программу 'source the_script.sh'. Это не может найти это, следовательно "No such file or directory" ошибка.

Broken Popen("source the_script.sh", shell=True) завершается неудачно, потому что source является встроенной командой bash (введите help source в bash), но оболочкой по умолчанию является /bin/sh, которая не понимает ее (/bin/sh использует .). Предполагая, что в the_script.sh может быть другой bash-ism, его следует запустить, используя bash:

foo = Popen("source the_script.sh", shell=True, executable="/bin/bash")

Как сказал @ IfLoop , не очень полезно выполнять source в подпроцессе, потому что это не может повлиять на среду родителя.

Методы

os.environ.update(env) завершаются неудачно, если the_script.sh выполняет unset для некоторых переменных. os.environ.clear() можно вызвать для сброса окружения:

#!/usr/bin/env python
import os
from pprint import pprint
from subprocess import check_output

os.environ['a'] = 'a'*100
# POSIX: name shall not contain '=', value doesn't contain '\0'
output = check_output("source the_script.sh; env -0",   shell=True,
                      executable="/bin/bash")
# replace env
os.environ.clear() 
os.environ.update(line.partition('=')[::2] for line in output.split('\0'))
pprint(dict(os.environ)) #NOTE: only `export`ed envvars here

Используются env -0 и .split('\0'), предложенные @ unutbu

Для поддержки произвольных байтов в os.environb можно использовать модуль json (при условии, что мы используем версию Python, где "json.dumps не разбирается по json.loads", проблема исправлена):

Чтобы избежать передачи среды по каналам, код Python можно изменить так, чтобы он вызывал себя в среде подпроцесса, например ::1010 *

#!/usr/bin/env python
import os
import sys
from pipes import quote
from pprint import pprint

if "--child" in sys.argv: # executed in the child environment
    pprint(dict(os.environ))
else:
    python, script = quote(sys.executable), quote(sys.argv[0])
    os.execl("/bin/bash", "/bin/bash", "-c",
        "source the_script.sh; %s %s --child" % (python, script))
2 голосов
/ 12 августа 2011

source - встроенная оболочка, специфичная для bash (а неинтерактивные оболочки часто представляют собой легкие оболочки-тире вместо bash).Вместо этого просто позвоните /bin/sh:

foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
1 голос
/ 19 сентября 2013

Вариант ответа от @xApple, поскольку иногда полезно иметь возможность создавать сценарий оболочки (а не файл Python) для установки переменных среды и, возможно, выполнять другие операции оболочки, а затем распространять эту среду наИнтерпретатор Python вместо потери этой информации при закрытии подоболочки.

Причина отклонения заключается в том, что предположение о формате вывода с одной переменной на строку из env не является надежным на 100%:Мне просто пришлось иметь дело с переменной (я думаю, что это функция оболочки), содержащей символ новой строки, который испортил этот синтаксический анализ.Итак, вот немного более сложная версия, которая использует сам Python для надежного форматирования словаря среды:

import subprocess
pipe = subprocess.Popen(". ./shellscript.sh; python -c 'import os; print \"newenv = %r\" % os.environ'", 
    stdout=subprocess.PIPE, shell=True)
exec(pipe.communicate()[0])
os.environ.update(newenv)

Может быть, есть более аккуратный способ?Это также гарантирует, что синтаксический анализ среды не будет испорчен, если кто-то вставит выражение echo в исходный скрипт.Конечно, здесь есть exec, так что будьте осторожны с ненадежным вводом ... но я думаю, что это подразумевается в обсуждении того, как получить / выполнить произвольный скрипт оболочки; -)

ОБНОВЛЕНИЕ: см. Комментарий @ unutbu к ответу @xApple об альтернативном (возможно, более хорошем) способе обработки перевода строки в выводе env.

0 голосов
/ 29 декабря 2013

Кажется, на это есть много ответов, не прочитал их все, поэтому они, возможно, уже указали на это;но при вызове таких команд оболочки вы должны передать shell = True вызову Popen.В противном случае вы можете вызвать Popen (shlex.split ()).убедитесь, что импортировали шлекс.

Я на самом деле использую эту функцию для получения файла и изменения текущей среды.

def set_env(env_file):
    while True:
        source_file = '/tmp/regr.source.%d'%random.randint(0, (2**32)-1)
        if not os.path.isfile(source_file): break
    with open(source_file, 'w') as src_file:
        src_file.write('#!/bin/bash\n')
        src_file.write('source %s\n'%env_file)
        src_file.write('env\n')
    os.chmod(source_file, 0755)
    p = subprocess.Popen(source_file, shell=True,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = p.communicate()
    setting = re.compile('^(?P<setting>[^=]*)=')
    value = re.compile('=(?P<value>.*$)')
    env_dict = {}
    for line in out.splitlines():
        if setting.search(line) and value.search(line):
            env_dict[setting.search(line).group('setting')] = value.search(line).group('value')
    for k, v in env_dict.items():
        os.environ[k] = v
    for k, v in env_dict.items():
        try:
            assert(os.getenv(k) == v)
        except AssertionError:
            raise Exception('Unable to modify environment')
0 голосов
/ 27 декабря 2012

Если вы хотите применить исходную команду к некоторым другим сценариям или исполняемым файлам, то вы можете создать другой файл сценария упаковки и вызвать из него команду «source» с любой другой необходимой вам логикой.В этом случае эта исходная команда изменит локальный контекст, в котором она выполняется, а именно в подпроцессе, который создает subprocess.Popen.

Это не будет работать, если вам нужно изменить контекст Python, в котором работает ваша программа.

...