Вызов команды "source" из подпроцесса. Popen
У меня есть .sh script, который я вызываю с помощью source the_script.sh
. Регулярно звоните нормально. Тем не менее, я пытаюсь назвать это из моего python script, через 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?
Ответы
Ответ 1
source
не является исполняемой командой, она встроена в оболочку.
Наиболее обычным случаем использования source
является запуск оболочки script, которая изменяет среду и сохраняет эту среду в текущей оболочке. Это точно, как virtualenv работает для изменения среды python по умолчанию.
Создание подпроцесса и использование source
в подпроцессе, вероятно, ничего не пригодится, оно не изменит среду родительского процесса, ни один из побочных эффектов использования источника script не будет имеют место.
Python имеет аналогичную команду execfile
, которая запускает указанный файл, используя текущее глобальное пространство имен python (или другое, если вы его поставляете), которое можно использовать аналогично команде bash source
.
Ответ 2
Вы можете просто запустить команду в подоболочке и использовать результаты для обновления текущей среды.
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)
Ответ 3
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)
-based завершаются ошибкой, если the_script.sh
выполняет unset
для некоторых переменных. os.environ.clear()
может быть вызван для сброса среды:
#!/usr/bin/env python2
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 not parsable by json.loads"):
Чтобы избежать передачи среды через каналы, код Python можно изменить так, чтобы он вызывал себя в среде подпроцесса, например:
#!/usr/bin/env python2
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))
Ответ 4
source
- это встроенная оболочка bash -специфическая оболочка (и неинтерактивные оболочки часто представляют собой легкие оболочки тире вместо bash). Вместо этого просто вызовите /bin/sh
:
foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
Ответ 5
Вариант ответа на @xApple, поскольку иногда полезно, чтобы он мог установить оболочку script (а не файл 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 в script, который будет использоваться. Конечно, здесь есть exec, поэтому будьте осторожны с недоверенным вводом... но я думаю, что это подразумевается в обсуждении того, как использовать/запускать произвольную оболочку script; -)
UPDATE: см. комментарий @unutbu в ответ @xApple для альтернативного (возможно, более приятного) способа обработки строк в env
вывода.
Ответ 6
Если вы хотите применить исходную команду к некоторым другим скриптам или исполняемым файлам, тогда вы можете создать другой файл wrap script и вызвать из него команду "source" с любой необходимой логикой. В этом случае эта команда источника изменит локальный контекст, в котором он выполняется, а именно в подпроцессе, который создает подпроцесс .Popen.
Это не сработает, если вам нужно изменить контекст python, в котором работает ваша программа.
Ответ 7
Кажется, на это много ответов, не прочитали их все, чтобы они уже указали это; но при вызове команд оболочки, подобных этому, вы должны передать shell = True в вызов Popen. В противном случае вы можете вызвать Popen (shlex.split()). обязательно импортируйте shlex.
Я использую эту функцию для поиска файла и изменения текущей среды.
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')