调用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')