Вызов команды "source" из subprocess.Popen
У меня есть скрипт .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
Что происходит? Почему я не могу вызвать "source" из Popen, когда у меня это получается сделать в командной строке?
5 ответ(ов)
source
— это не исполняемая команда, а встроенная команда оболочки.
Самый распространенный случай использования source
— это выполнение скрипта оболочки, который изменяет окружение, чтобы сохранить это окружение в текущей оболочке. Именно так работает virtualenv
, изменяя стандартное окружение Python.
Создание подпроцесса и использование source
в этом подпроцессе, скорее всего, не даст ничего полезного, так как это не изменит окружение родительского процесса и никакие побочные эффекты выполнения подключаемого скрипта не произойдут.
В Python есть аналогичная команда execfile
, которая выполняет указанный файл с использованием текущего глобального пространства имен Python (или другого, если вы его укажете). Эту команду можно использовать аналогично команде source
в bash.
Вы можете просто выполнить команду в подсохранении и использовать полученные результаты для обновления текущей среды.
def shell_source(script):
"""Иногда вам нужно эмулировать действие "source" в bash,
устанавливая некоторые переменные окружения. Вот как это можно сделать."""
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)
Этот код выполняет скрипт в оболочке и собирает все переменные окружения, которые были установлены в результате выполнения скрипта. Затем он обновляет переменные окружения текущего процесса. Обратите внимание, что такой подход работает только для текущего процесса и не затрагивает родительский процесс.
source
— это встроенная команда, специфичная для bash, и в неинтерактивных оболочках обычно используются более легковесные оболочки, такие как dash. Вместо этого просто вызовите /bin/sh
:
foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
Таким образом, вы сможете выполнить скрипт в совместимой оболочке.
Обновление: 2019
Иногда вам нужно эмулировать действие команды "source" в Bash для установки некоторых переменных окружения. Вот один из способов сделать это.
def shell_source(str_script, lst_filter):
# Обходная мера для разрешения использования переменных с переносами строк
# Пример: MY_VAR='foo\n'
# env -i создает чистую оболочку
# bash -c выполняет команду в bash
# set -a опционально, если вы хотите экспортировать как переменные оболочки, так и переменные среды
# env -0 разделяет переменные нулевым символом вместо символа новой строки
command = shlex.split(f"env -i bash -c 'set -a && source {str_script} && env -0'")
pipe = subprocess.Popen(command, stdout=subprocess.PIPE)
# pipe теперь выводит байты, поэтому преобразуем в строку utf перед разбором
output = pipe.communicate()[0].decode('utf-8')
# У меня всегда была последняя пустая строка, поэтому я удаляю ее. Удалите эту строку, если у вас это не происходит.
output = output[:-1]
pre_filter_env = {}
# Разделяем, используя нулевой символ
for line in output.split('\x00'):
line = line.split('=', 1)
pre_filter_env[line[0]] = line[1]
post_filter_env = {}
for str_var in lst_filter:
post_filter_env[str_var] = pre_filter_env[str_var]
os.environ.update(post_filter_env)
Этот код запускает Bash в чистой среде и использует source
для выполнения указанного скрипта, затем извлекает переменные окружения и обновляет текущую среду Python. Подход отлично подходит для тех случаев, когда необходимо интерактивно управлять переменными окружения из внешних скриптов.
Используя ответы из этой темы, я разработал решение, которое полностью удовлетворяет мои потребности.
- нет необходимости фильтровать переменные окружения
- позволяет использовать переменные с символами новой строки
def shell_source(script):
"""
Иногда вам нужно эмулировать действие "source" в bash,
устанавливая некоторые переменные окружения. Вот способ сделать это.
"""
pipe = subprocess.Popen(". %s && env -0" % script, stdout=subprocess.PIPE, shell=True)
output = pipe.communicate()[0].decode('utf-8')
output = output[:-1] # исправление для индекса вне диапазона в 'env[ line[0] ] = line[1]'
env = {}
# делим по нулевому символу
for line in output.split('\x00'):
line = line.split('=', 1)
env[line[0]] = line[1]
os.environ.update(env)
С этим решением я смог запускать команды с теми же переменными окружения без проблем:
def runCommand(command):
"""
Печатает, а затем выполняет команду оболочки.
"""
print(f'> выполняется: {command}')
stream = subprocess.Popen(command, shell=True, env=os.environ)
(result_data, result_error) = stream.communicate()
print(f'{result_data}, {result_error}')
Надеюсь, это поможет кому-то, кто оказался в такой же ситуации, как я.
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата
Почему statistics.mean() работает так медленно?
Преобразование строки даты JSON в datetime в Python
Есть ли разница между поднятием экземпляра класса Exception и самого класса Exception?