从 subprocess.Popen 调用“source”命令
- 2025-02-20 09:23:00
- admin 原创
- 31
问题描述:
我有一个用 调用的 .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 调用“source”,而可以在 Python 之外调用?
解决方案 1:
source
不是可执行命令,而是 shell 内置命令。
最常见的使用情况source
是运行更改环境的 shell 脚本,并在当前 shell 中保留该环境。这正是 virtualenv 修改默认 python 环境的工作原理。
创建子进程并source
在子进程中使用可能不会做任何有用的事情,它不会修改父进程的环境,也不会发生使用源脚本的任何副作用。
Python 有一个类似的命令,execfile
它使用当前 python 全局命名空间(或另一个,如果您提供的话)运行指定的文件,您可以以类似于 bash 命令的方式使用它source
。
解决方案 2:
您可以在子 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)
解决方案 3:
BrokenPopen("source the_script.sh")
相当于Popen(["source the_script.sh"])
尝试启动'source the_script.sh'
程序失败。找不到它,因此"No such file or directory"
出错。
BrokenPopen("source the_script.sh", shell=True)
失败,因为source
是 bash 内置命令(help source
在 bash 中输入),但默认 shell/bin/sh
无法理解它(/bin/sh
使用.
)。假设中可能还有其他 bash-ism the_script.sh
,则应使用 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 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 ' '
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(' '))
pprint(dict(os.environ)) #NOTE: only `export`ed envvars here
它使用env -0
并由.split(' ')
@unutbu 建议
为了支持中的任意字节os.environb
,json
可以使用模块(假设我们使用的 Python 版本已修复“json.dumps 无法被 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 专用 shell(非交互式 shell 通常是轻量级 dash shell,而不是 bash)。相反,只需调用/bin/sh
:
foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
解决方案 5:
更新: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
'
#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 )
解决方案 6:
@xApple 的答案的一个变体,因为有时能够获取 shell 脚本(而不是 Python 文件)来设置环境变量并可能执行其他 shell 操作,然后将该环境传播到 Python 解释器而不是在子 shell 关闭时丢失该信息很有用。
之所以要进行修改,是因为“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 对@xApple 答案的评论,了解处理输出中的换行符的另一种(可能更好)方法env
。
解决方案 7:
利用这里的答案,我创建了一个符合我需要的解决方案。
不需要过滤环境变量
允许使用换行符的变量
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}')
希望这能帮助和我处境相同的人
解决方案 8:
如果您想将 source 命令应用于其他脚本或可执行文件,那么您可以创建另一个包装脚本文件,并使用您需要的任何其他逻辑从中调用“source”命令。在这种情况下,此 source 命令将修改其运行的本地上下文 - 即 subprocess.Popen 创建的子进程中。
如果您需要修改程序正在运行的 python 上下文,那么这将不起作用。
解决方案 9:
这个问题似乎有很多答案,我还没有全部读完,所以他们可能已经指出了这一点;但是,当调用这样的 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
')
src_file.write('source %s
'%env_file)
src_file.write('env
')
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')
- 2025年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)