在 Python 中模拟 Bash ‘source’
- 2025-02-27 09:07:00
- admin 原创
- 57
问题描述:
我有一个如下所示的脚本:
export foo=/tmp/foo
export bar=/tmp/bar
每次构建时,我都会运行“source init_env”(其中 init_env 是上述脚本)来设置一些变量。
为了在 Python 中实现同样的功能,我运行了这段代码,
reg = re.compile('export (?P<name>w+)(=(?P<value>.+))*')
for line in open(file):
m = reg.match(line)
if m:
name = m.group('name')
value = ''
if m.group('value'):
value = m.group('value')
os.putenv(name, value)
但后来有人认为最好在init_env
文件中添加如下一行:
export PATH="/foo/bar:/bar/foo:$PATH"
显然我的 Python 脚本崩溃了。我可以修改 Python 脚本来处理此行,但当有人想出一个新功能在文件中使用时,它就会崩溃init_env
。
问题是是否有一种简单的方法来运行 Bash 命令并让它修改我的os.environ
?
解决方案 1:
您的方法存在问题,因为您尝试解释 bash 脚本。首先,您只是尝试解释导出语句。但是当人们认为他们可以使用 bash 语法时,他们就会这样做。他们将使用变量扩展、条件、进程替换。最后,您将得到一个功能齐全的 bash 脚本解释器,其中有无数的错误。不要这样做。
让 Bash 为您解释文件,然后收集结果。
这是一个最简单的例子,说明如何做到这一点:
#! /usr/bin/env python
import os
import pprint
import shlex
import subprocess
command = shlex.split("bash -c 'source init_env && env'")
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
for line in proc.stdout:
(key, _, value) = line.partition("=")
os.environ[key] = value
proc.communicate()
pprint.pprint(dict(os.environ))
确保处理错误。请参阅此处了解如何:“subprocess.Popen” - 检查成功和错误
另请阅读有关subprocess的文档。
这将只捕获用export
语句设置的变量,因为env
只打印导出的变量。您可以添加set -a
将所有变量视为导出。
command = shlex.split("bash -c 'set -a && source init_env && env'")
^^^^^^
请注意,此代码不会处理多行变量。它也不会处理 bash 函数定义。
也许比在 python 内部调用 bash source 更好的方法是先让 bash source 文件,然后运行 python 脚本
#!/bin/bash
source init_env
/path/to/python_script.py
这里 bash 将source init_env
拥有 bash 的所有功能、优势和怪癖。python 脚本将继承更新的环境。
请注意,只有导出的变量才会被继承。你可以强制导出所有变量赋值set -a
#!/bin/bash
set -a
source init_env
/path/to/python_script.py
另一种方法是告诉用户他们严格地只能在key=value
没有任何 bash 功能的情况下执行操作。然后使用 python configparser。
这具有init_env
语法简单和经过严格测试的配置解析器的优点。但缺点是init_env
不再像 bash 配置文件那样具有表现力。
解决方案 2:
使用 pickle:
import os, pickle
# For clarity, I moved this string out of the command
source = 'source init_env'
dump = '/usr/bin/python -c "import os,pickle;print pickle.dumps(os.environ)"'
penv = os.popen('%s && %s' %(source,dump))
env = pickle.loads(penv.read())
os.environ = env
更新:
这使用 json、subprocess,并明确使用 /bin/bash(用于 ubuntu 支持):
import os, subprocess as sp, json
source = 'source init_env'
dump = '/usr/bin/python -c "import os, json;print json.dumps(dict(os.environ))"'
pipe = sp.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=sp.PIPE)
env = json.loads(pipe.stdout.read())
os.environ = env
解决方案 3:
init_env
与使用 Python 脚本获取 bash 脚本的源代码相比,使用包装器脚本源代码然后使用修改后的环境运行 Python 脚本会更简单、更优雅。
#!/bin/bash
source init_env
/run/python/script.py
解决方案 4:
更新了@lesmana 对 Python 3 的回答。请注意,使用env -i
它可防止设置/重置多余的环境变量(由于缺乏对多行环境变量的处理,可能会出现错误)。
import os, subprocess
if os.path.isfile("init_env"):
command = 'env -i sh -c "source init_env && env"'
for line in subprocess.getoutput(command).split("
"):
key, value = line.split("=")
os.environ[key]= value
解决方案 5:
将@Brian 的优秀答案包装在函数中的示例:
import json
import subprocess
# returns a dictionary of the environment variables resulting from sourcing a file
def env_from_sourcing(file_to_source_path, include_unexported_variables=False):
source = '%ssource %s' % ("set -a && " if include_unexported_variables else "", file_to_source_path)
dump = '/usr/bin/python -c "import os, json; print json.dumps(dict(os.environ))"'
pipe = subprocess.Popen(['/bin/bash', '-c', '%s && %s' % (source, dump)], stdout=subprocess.PIPE)
return json.loads(pipe.stdout.read())
我正在使用此实用函数读取 aws 凭证和 docker .env 文件include_unexported_variables=True
。
解决方案 6:
我发现的最佳解决方法是这样的:
编写一个包装器 bash 脚本来调用你的 python 脚本
在该 bash 脚本中,你可以在获取当前终端后获取或调用该脚本
扫码咨询,免费领取项目管理大礼包!