调用subprocess.Popen中的“source”命令
我有一个.sh脚本,我打电话给source the_script.sh
。 定期打电话很好。 不过,我想通过subprocess.Popen
从我的python脚本调用它。
从Popen调用它,我得到以下两个scheme调用中的以下错误:
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之外?
source
不是一个可执行的命令,这是一个内置的shell。
使用source
最常见的情况是运行一个shell脚本来改变环境并在当前shell中保留这个环境。 这就是virtualenv如何修改默认的python环境。
在subprocess中创build一个subprocess和使用source
可能不会做任何有用的事情,它不会修改父进程的环境,也不会产生使用源脚本的副作用。
Python有一个类似的命令execfile
,它使用当前的python全局命名空间(或另一个,如果你提供的话)运行指定的文件,你可以使用类似于bash命令source
。
您可以在子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)
Broken Popen("source the_script.sh")
等同于尝试启动'source the_script.sh'
程序失败的Popen(["source the_script.sh"])
。 它无法find它,因此"No such file or directory"
错误。
Broken Popen("source the_script.sh", shell=True)
失败,因为source
是一个bash内build命令(在bash中inputhelp source
),但默认的shell是/bin/sh
,它不理解它( /bin/sh
使用)。 假设在the_script.sh
可能有其他的bash-ism,它应该使用bash运行:
foo = Popen("source the_script.sh", shell=True, executable="/bin/bash")
正如@IfLoop所说 ,在subprocess中执行source
并不是很有用,因为它不会影响父进程的环境。
os.environ.update(env)
的方法失败,如果the_script.sh
执行一些variables未unset
。 可以调用os.environ.clear()
来重置环境:
#!/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
它使用@unutbubuild议的env -0
和.split('\0')
为了支持os.environb
任意字节,可以使用json
模块(假设我们使用Python版本,其中“json.dumps不能被json.loadsparsing”问题是固定的):
为了避免通过pipe道传递环境,可以改变Python代码以在subprocess环境中调用自身,例如:
#!/usr/bin/env python 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))
source
是内置的bash特定的shell(而非交互式shell通常是轻量级的破折号而不是bash)。 相反,只需调用/bin/sh
:
foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
@xApple的答案的一个变种,因为它有时能够获取shell脚本(而不是Python文件)来设置环境variables,并可能执行其他shell操作,然后将该环境传播到Python解释器而不是在子shellclosures时丢失这些信息。
一个变化的原因是,从“env”输出的单行variables格式假设不是100%稳健的:我只需要处理一个variables(我认为是一个shell函数),它包含一个换行,这搞砸了parsing。 所以这里是一个稍微复杂一些的版本,它使用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语句放入正在发送的脚本中,环境parsing不会混乱。 当然,这里有一个exec,所以要小心不可信任的input……但是我认为这是隐含在讨论如何发送/执行一个任意的shell脚本;-)
更新:请参阅@ unutbu关于@xApple答案的评论 ,以替代(可能更好)的方式来处理env
输出中的换行符。
如果你想将源代码命令应用到其他脚本或可执行文件,那么你可以创build另一个包装脚本文件,并使用你需要的任何逻辑来调用“源代码”命令。 在这种情况下,这个源命令会修改运行的本地上下文 – 即在subprocess.Popen创build的subprocess中。
如果您需要修改程序运行的python上下文,这将不起作用。
这似乎有很多的答案,没有阅读所有这些,所以他们可能已经指出了; 但是,像这样调用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')