2011-08-12 64 views
20

我有一个.sh脚本,我打电话给source the_script.sh。定期打电话很好。但是,我试图从我的python脚本通过subprocess.Popen来调用它。从子进程调用“源”命令.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中调用“源代码”,当我可以在Python之外?

+0

[Python中模拟Bash'source']的可能重复(https://stackoverflow.com/questions/3503719/emulating-bash-source-in-python) – sds

回答

16

source不是一个可执行的命令,这是一个shell内建。

使用source的最常见情况是运行一个shell脚本,用于更改环境并在当前shell中保留该环境。这正是virtualenv如何工作来修改默认的python环境。

在子流程中创建子流程和使用source可能不会做任何有用的事情,它不会修改父流程的环境,也不会发生使用源脚本的副作用。

Python有类似的命令,execfile,运行使用当前的Python全局命名空间中指定的文件(或另一个,如果你提供一个),你可以用类似的方式bash命令source使用。

+1

另请注意,虽然'execfile'确实是类似的,但在Python程序中,'import'几乎总是用在你通常在shell脚本中使用'source'的地方。 –

+0

有趣。所以,如果我按照phihag的建议去做,环境变量的任何更改都不会实际上保持不变? – coffee

+0

好吧,他们会坚持在bash子进程中,但是那会对你有什么好处取决于'the_script.sh'实际上做了什么。通过'source'调用的脚本不太可能在子进程中有很大用处。 – SingleNegationElimination

1

source是内置的bash特定的shell(并且非交互式shell通常是轻量级的破折号而不是bash)。相反,只需拨打/bin/sh

foo = subprocess.Popen(["/bin/sh", "the_script.sh"]) 
+0

如果'the_script.sh'具有合适的shebang和权限('+ x'),那么'foo = subprocess.Popen(“./ the_script.sh”)'应该可以工作。 – jfs

24

您可以在子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

归功于它:这来自http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script。html(尽管可能是xApple == Miki?)但是,一个注意事项是:通常脚本参数需要是一个明确的路径,即“myenv.sh”一般不起作用,但“./myenv.sh”会。这是因为在严格实施sh shell(如Debian/Ubuntu)的系统上采购内置(。)的行为。 – andybuckley

+0

@andybuckley评论正确。使用“./myenv.sh”而不是“myenv.sh”。 – diabloneo

+3

如果环境变量的值包含换行符,此函数可能会引发'ValueError'。要[修复](http://stackoverflow.com/a/20669683/190597),请使用'env -0'和'output.split('\ x00')'。 – unutbu

0

如果你想源命令适用于某些其他脚本或可执行文件,那么你可能会创建另一个包裹脚本文件,并称之为“源”,从它与你需要的任何进一步的逻辑命令。在这种情况下,该源命令将修改运行的本地上下文 - 即在subprocess.Popen创建的子进程中。

如果您需要修改运行程序的python上下文,这将不起作用。

1

@xApple答案的一种变化,因为它有时可以获取shell脚本(而不是Python文件)来设置环境变量,并可能执行其他shell操作,然后将该环境传播到Python解释器而不是在子shell关闭时丢失这些信息。

变化的原因是,来自“env”的输出的单变量每行格式的假设不是100%稳健的:我只需要处理一个变量(一个shell函数,I认为)包含一个换行符,这搞砸了解析。因此,这里是一个稍微复杂的版本,它使用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语句放入正在发送的脚本中,环境解析不会混乱。当然,这里有一位高管,所以要小心不信任的输入...但我认为这是一个关于如何采购/执行任意shell脚本;-)

UPDATE讨论隐:看到@unutbu's comment on the @xApple answer一种替代(可能更好)的方式来处理在env输出换行。

+1

如果'。/ shellscript.sh'取消某些变量,则'os.environ.update()'方法失败。 ['os.environ.clear()'可以使用。](http://stackoverflow.com/a/22086176/4279)。你可以使用'json.dumps(dict(os.environ))'和'json.loads(output)'而不是''%r''和'exec'。尽管简单['env -0'和'.split('\ 0')'在这里工作得很好](http://stackoverflow.com/a/22086176/4279)。 – jfs

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') 
10

破碎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不理解它(/bin/sh使用.)。假设可能有其他的bash主义在the_script.sh,应该使用bash运行:

foo = Popen("source the_script.sh", shell=True, executable="/bin/bash") 

由于@IfLoop said,它不是非常有用的一个子进程来执行source,因为它可以在不影响父母的环境。

os.environ.update(env)如果the_script.sh对某些变量执行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 

它采用env -0 and .split('\0') suggested by @unutbu

为了支持任意字节os.environbjson模块可以使用(假设我们使用Python版本,其中"json.dumps not parsable by json.loads" issue是固定的):

为了避免通过管道传递环境,可以将Python代码更改为在子进程环境中自行调用,例如:

#!/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))