我有一个用source the_script.sh
调用的.sh脚本。定期打电话很好。但是,我试图通过subprocess.Popen
从我的python脚本中调用它。
从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中调用“source”,当我可以在python之外?
答案 0 :(得分:27)
您可以在子shell中运行该命令,并使用结果更新当前环境。
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)
答案 1 :(得分:23)
source
不是可执行命令,它是内置的shell。
使用source
的最常见情况是运行一个shell脚本来更改环境并在当前shell中保留该环境。这正是virtualenv如何修改默认的python环境。
创建子流程并在子流程中使用source
可能不会做任何有用的事情,它不会修改父流程的环境,使用源脚本的任何副作用都不会发生。
Python有一个类似的命令execfile
,它使用当前的python全局命名空间(或另一个,如果你提供的)运行指定的文件,你可以使用与bash命令类似的方式{{} 1}}。
答案 2 :(得分:13)
已损坏的Popen("source the_script.sh")
相当于尝试启动Popen(["source the_script.sh"])
计划失败的'source the_script.sh'
。它无法找到它,因此"No such file or directory"
错误。
已损坏Popen("source the_script.sh", shell=True)
失败,因为source
是bash内置命令(在bash中键入help source
),但默认shell为/bin/sh
,不理解它({{ 1}}使用/bin/sh
)。假设.
中可能存在其他bash-ism,它应该使用bash运行:
the_script.sh
作为@IfLoop said,在子进程中执行foo = Popen("source the_script.sh", shell=True, executable="/bin/bash")
并不是非常有用,因为它不会影响父进程的环境。
source
对某些变量执行os.environ.update(env)
,则基于the_script.sh
的方法会失败。可以调用unset
来重置环境:
os.environ.clear()
它使用env -0
and .split('\0')
suggested by @unutbu
为了支持#!/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
中的任意字节,可以使用os.environb
模块(假设我们使用固定"json.dumps not parsable by json.loads" issue的Python版本):
为避免通过管道传递环境,可以更改Python代码以在子进程环境中调用自身,例如:
json
答案 3 :(得分:2)
source
是一个内置的特定于bash的shell(非交互式shell通常是轻量级的破坏shell而不是bash)。相反,只需致电/bin/sh
:
foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
答案 4 :(得分:1)
@xApple对答案的一种变体,因为它有时能够获取shell脚本(而不是Python文件)来设置环境变量,并且可能执行其他shell操作,然后将该环境传播到在子shell关闭时,Python解释器而不是丢失该信息。
变化的原因是“env”输出的每行一变量格式的假设不是100%稳健的:我只需处理变量(shell函数,我认为)包含一个新行,这搞砸了解析。所以这里有一个稍微复杂的版本,它使用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,所以要小心不信任的输入......但我认为这是关于如何获取/执行任意shell脚本的讨论中隐含的; - )
更新:请参阅@unutbu's comment on the @xApple answer了解处理env
输出中换行符的替代方法(可能更好)。
答案 5 :(得分:0)
如果要将源命令应用于某些其他脚本或可执行文件,则可以创建另一个包装脚本文件,并使用您需要的任何其他逻辑从中调用“source”命令。在这种情况下,此源命令将修改它运行的本地上下文 - 即在subprocess.Popen创建的子进程中。
如果你需要修改运行程序的python上下文,这将不起作用。
答案 6 :(得分:0)
似乎有很多答案,没有阅读所有这些,所以他们可能已经指出了;但是,当调用这样的shell命令时,你必须将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')
答案 7 :(得分:0)
更新:2019年
"""
Sometime you want to emulate the action of "source" in bash,
settings some environment variables. Here is a way to do it.
"""
def shell_source( str_script, lst_filter ):
#work around to allow variables with new lines
#example MY_VAR='foo\n'
#env -i create clean shell
#bash -c run bash command
#set -a optional include if you want to export both shell and enrivonment variables
#env -0 seperates variables with null char instead of newline
command = shlex.split(f"env -i bash -c 'set -a && source {str_script} && env -0'")
pipe = subprocess.Popen( command, stdout=subprocess.PIPE )
#pipe now outputs as byte, so turn it to utf string before parsing
output = pipe.communicate()[0].decode('utf-8')
#There was always a trailing empty line for me so im removing it. Delete this line if this is not happening for you.
output = output[:-1]
pre_filter_env = {}
#split using null char
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 )
答案 8 :(得分:0)
使用此处的答案,我创建了适合自己需求的解决方案。
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.
"""
pipe = subprocess.Popen(". %s && env -0" % script, stdout=subprocess.PIPE, shell=True)
output = pipe.communicate()[0].decode('utf-8')
output = output[:-1] # fix for index out for range in 'env[ line[0] ] = line[1]'
env = {}
# split using null char
for line in output.split('\x00'):
line = line.split( '=', 1)
# print(line)
env[ line[0] ] = line[1]
os.environ.update(env)
有了这个,我能够在没有问题的情况下使用相同的环境变量运行命令:
def runCommand(command):
"""
Prints and then runs shell command.
"""
print(f'> running: {command}')
stream = subprocess.Popen(command, shell=True,env=os.environ)
(result_data, result_error) = stream.communicate()
print(f'{result_data}, {result_error}')
希望这可以帮助与我处于同一职位的任何人