2011-08-12 170 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))