2011-08-12 43 views
20

Tôi có một tập lệnh .sh mà tôi gọi với source the_script.sh. Gọi điều này thường xuyên là tốt. Tuy nhiên, tôi đang cố gắng gọi nó từ kịch bản python của tôi, thông qua subprocess.Popen.Gọi lệnh "nguồn" từ subprocess.Popen

Gọi nó từ Popen, tôi nhận được lỗi sau đây trong hai kịch bản sau đây gọi:

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 

gì cho? Tại sao tôi không thể gọi "nguồn" từ Popen, khi tôi có thể ở bên ngoài python?

+0

Có thể trùng lặp của [Emulating Bash 'nguồn' trong Python] (https://stackoverflow.com/questions/3503719/emulating-bash-source-in-python) – sds

Trả lời

16

source không phải là một lệnh thực thi, đó là một BUILTIN vỏ.

Trường hợp thông thường nhất để sử dụng source là chạy tập lệnh trình bao thay đổi môi trường và giữ lại môi trường đó trong trình bao hiện tại. Đó chính xác là cách virtualenv hoạt động để sửa đổi môi trường python mặc định.

Tạo quy trình con và sử dụng source trong quy trình con có thể sẽ không làm bất kỳ điều gì hữu ích, nó sẽ không sửa đổi môi trường của quy trình gốc, không có tác dụng phụ nào khi sử dụng tập lệnh gốc.

Python có lệnh tương tự, execfile, chạy tệp được chỉ định bằng cách sử dụng không gian tên toàn cầu python hiện tại (hoặc tên khác, nếu bạn cung cấp), bạn có thể sử dụng theo cách tương tự như lệnh bash source.

+1

Cũng lưu ý rằng trong khi 'execfile' là tương tự chính xác, trong các chương trình Python' import' sẽ hầu như luôn được sử dụng khi bạn thường sử dụng 'source' trong một kịch bản lệnh shell. –

+0

Thú vị. Vì vậy, evne nếu tôi làm như đề nghị của phihag, bất kỳ thay đổi nào với biến môi trường sẽ không thực sự dính? – coffee

+0

tốt, họ sẽ dính vào bash subprocess, nhưng những gì tốt mà sẽ làm bạn phụ thuộc vào những gì 'the_script.sh' thực sự không. Nó không chắc rằng một kịch bản có nghĩa là để được gọi thông qua 'nguồn' là sử dụng nhiều trong một tiến trình con. – SingleNegationElimination

1

source là một vỏ bash-cụ thể tích hợp (và không tương tác vỏ thường vỏ nhẹ dash thay vì bash). Thay vào đó, chỉ cần gọi /bin/sh:

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

nếu 'the_script.sh' có đúng shebang và quyền ('+ x') thì' foo = subprocess.Popen ("./ the_script.sh") 'sẽ hoạt động. – jfs

24

Bạn chỉ có thể chạy lệnh trong một vỏ con và sử dụng kết quả để cập nhật môi trường hiện tại.

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

Khoản tín dụng đến hạn: số này đến từ http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.Một lưu ý, mặc dù: nói chung đối số tập lệnh cần phải là một đường dẫn rõ ràng, tức là "myenv.sh" sẽ không hoạt động nói chung, nhưng "./myenv.sh" sẽ. Điều này là do hành vi của việc tìm nguồn cung ứng tích hợp (.) Trên các hệ thống có thực thi nghiêm ngặt vỏ sh (như Debian/Ubuntu). – andybuckley

+0

@andybuckley đã nhận xét đúng. Sử dụng "./myenv.sh" thay vào đó "myenv.sh". – diabloneo

+3

Hàm này có thể tăng 'ValueError' nếu giá trị của biến môi trường chứa các dòng mới. Để [sửa] (http://stackoverflow.com/a/20669683/190597), sử dụng 'env -0' và' output.split ('\ x00') '. – unutbu

0

Nếu bạn muốn áp dụng lệnh nguồn cho một số tập lệnh hoặc tệp thực thi khác, bạn có thể tạo một tệp tập lệnh gói khác và gọi lệnh "nguồn" từ đó. Trong trường hợp đó, lệnh nguồn này sẽ sửa đổi bối cảnh cục bộ nơi nó đang chạy - cụ thể là trong tiến trình con mà subprocess.Popen tạo ra.

Điều này sẽ không hoạt động nếu bạn cần sửa đổi ngữ cảnh python, nơi chương trình của bạn đang chạy.

1

Biến thể về câu trả lời bằng @xApple vì đôi khi rất hữu ích để có thể mã nguồn tập lệnh shell (chứ không phải tệp Python) để đặt biến môi trường và có thể thực hiện các thao tác vỏ khác, sau đó lan truyền môi trường đó trình thông dịch Python thay vì mất thông tin đó khi đóng gói con.

Lý do cho biến thể là giả thiết định dạng đầu ra một biến trên mỗi dòng từ "env" không phải là 100% mạnh mẽ: tôi chỉ phải xử lý một biến (hàm shell, I suy nghĩ) có chứa một dòng mới, điều này đã làm hỏng việc phân tích cú pháp đó. Vì vậy, đây là một phiên bản hơi phức tạp hơn, trong đó sử dụng Python chính nó để định dạng từ điển môi trường một cách mạnh mẽ:

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) 

Có thể có một cách neater? Điều này cũng đảm bảo rằng việc phân tích cú pháp môi trường không bị rối loạn nếu ai đó đặt một câu lệnh echo vào tập lệnh đang được cung cấp. Tất nhiên, có một exec ở đây vì vậy hãy cẩn thận về đầu vào không đáng tin cậy ...nhưng tôi nghĩ đó là tiềm ẩn trong một cuộc thảo luận về làm thế nào để nguồn/thực hiện một kịch bản vỏ tùy ý ;-)

UPDATE: thấy @unutbu's comment on the @xApple answer cho một sự thay thế (có lẽ đẹp hơn) cách để xử lý dòng mới trong env đầu ra.

+1

'os.environ.update()' dựa trên phương pháp tiếp cận thất bại nếu './Shellscript.sh' vô hiệu hóa một số biến. ['os.environ.clear()' có thể được sử dụng.] (http://stackoverflow.com/a/22086176/4279). Bạn có thể 'json.dumps (dict (os.environ))' và 'json.loads (output)' thay vì ''% r'' và' exec'. Mặc dù đơn giản ['env -0' và' .split ('\ 0') 'hoạt động tốt ở đây] (http://stackoverflow.com/a/22086176/4279). – jfs

0

Dường như có rất nhiều câu trả lời cho điều này, chưa đọc tất cả chúng để chúng có thể đã chỉ ra; nhưng, khi gọi các lệnh shell như thế này, bạn phải chuyển shell = True thành lệnh Popen. Nếu không, bạn có thể gọi Popen (shlex.split()). đảm bảo nhập shlex.

Tôi thực sự sử dụng chức năng này cho mục đích tìm nguồn cung ứng tệp và sửa đổi môi trường hiện tại.

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

bị hỏng Popen("source the_script.sh") tương đương với Popen(["source the_script.sh"]) mà cố gắng không thành công để khởi động chương trình 'source the_script.sh'. Nó không thể tìm thấy nó, do đó lỗi "No such file or directory".

bị hỏng Popen("source the_script.sh", shell=True) thất bại vì source là một lệnh dựng sẵn bash (loại help source trong bash) nhưng vỏ mặc định là /bin/sh mà không hiểu nó (/bin/sh sử dụng .). Giả sử có thể có khác bash-ism trong the_script.sh, nó phải được chạy bằng bash:

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

Như @IfLoop said, nó không phải là rất hữu ích để thực hiện source trong một tiến trình con bởi vì nó không thể ảnh hưởng đến môi trường của cha mẹ.

os.environ.update(env) phương pháp dựa trên không thành công nếu the_script.sh thực thi unset đối với một số biến. os.environ.clear() có thể được gọi để thiết lập lại môi trường:

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

Nó sử dụng env -0 and .split('\0') suggested by @unutbu

Để hỗ trợ byte tùy ý trong os.environb, json mô-đun có thể được sử dụng (giả sử chúng tôi sử dụng phiên bản Python nơi "json.dumps not parsable by json.loads" issue là cố định):

Để tránh truyền môi trường qua đường ống, mã Python có thể được thay đổi để tự gọi trong môi trường xử lý con, ví dụ:

#!/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)) 
Các vấn đề liên quan