Mô phỏng 'nguồn' Bash bằng Python


91

Tôi có một tập lệnh trông giống như sau:

export foo=/tmp/foo                                          
export bar=/tmp/bar

Mỗi khi tôi xây dựng, tôi chạy 'source init_env' (trong đó init_env là tập lệnh ở trên) để thiết lập một số biến.

Để hoàn thành điều tương tự trong Python, tôi đã chạy mã này,

reg = re.compile('export (?P<name>\w+)(\=(?P<value>.+))*')
for line in open(file):
    m = reg.match(line)
    if m:
        name = m.group('name')
        value = ''
        if m.group('value'):
            value = m.group('value')
        os.putenv(name, value)

Nhưng sau đó ai đó quyết định sẽ rất tuyệt nếu thêm một dòng như sau vào init_envtệp:

export PATH="/foo/bar:/bar/foo:$PATH"     

Rõ ràng là tập lệnh Python của tôi đã bị hỏng. Tôi có thể sửa đổi tập lệnh Python để xử lý dòng này, nhưng sau đó nó sẽ chỉ bị hỏng sau khi ai đó đưa ra một tính năng mới để sử dụng trong init_envtệp.

Câu hỏi là nếu có một cách dễ dàng để chạy một lệnh Bash và để nó sửa đổi lệnh của tôi os.environ?


Câu trả lời:


109

Vấn đề với cách tiếp cận của bạn là bạn đang cố gắng diễn giải các tập lệnh bash. Đầu tiên, bạn chỉ cần cố gắng diễn giải câu lệnh xuất. Sau đó, bạn nhận thấy mọi người đang sử dụng mở rộng biến. Những người sau này sẽ đặt các điều kiện vào tệp của họ hoặc xử lý các thay thế. Cuối cùng, bạn sẽ có một trình thông dịch tập lệnh bash hoàn chỉnh với hàng đống lỗi. Đừng làm vậy.

Hãy để Bash giải thích tệp cho bạn và sau đó thu thập kết quả.

Bạn có thể làm như thế này:

#! /usr/bin/env python

import os
import pprint
import shlex
import subprocess

command = shlex.split("env -i bash -c 'source init_env && env'")
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
for line in proc.stdout:
  (key, _, value) = line.partition("=")
  os.environ[key] = value
proc.communicate()

pprint.pprint(dict(os.environ))

Đảm bảo rằng bạn xử lý các lỗi trong trường hợp bash không source init_envthực hiện được, hoặc bản thân bash không thực thi hoặc quy trình con không thực hiện bash hoặc bất kỳ lỗi nào khác.

các env -ivào đầu dòng lệnh tạo ra một môi trường sạch sẽ. điều đó có nghĩa là bạn sẽ chỉ nhận được các biến môi trường từ init_env. nếu bạn muốn môi trường hệ thống kế thừa thì bỏ qua env -i.

Đọc tài liệu về quy trình con để biết thêm chi tiết.

Lưu ý: điều này sẽ chỉ nắm bắt các biến được thiết lập với exportcâu lệnh, vì envchỉ in các biến đã xuất.

Thưởng thức.

Lưu ý rằng tài liệu Python nói rằng nếu bạn muốn thao tác với môi trường, bạn nên thao tác os.environtrực tiếp thay vì sử dụng os.putenv(). Tôi coi đó là một lỗi, nhưng tôi lạc đề.


12
Nếu bạn quan tâm đến các biến không được xuất và tập lệnh nằm ngoài tầm kiểm soát của bạn, bạn có thể sử dụng set -a để đánh dấu tất cả các biến là đã xuất. Chỉ cần thay đổi lệnh thành: ['bash', '-c', 'set -a && source init_env && env']
ahal 25/09/13

Lưu ý rằng điều này sẽ không thành công trên các chức năng đã xuất. Tôi rất thích nếu bạn có thể cập nhật câu trả lời của mình hiển thị phân tích cú pháp cũng hoạt động cho các hàm. (ví dụ: function fff () {echo "fff";}; export -f fff)
DA

2
Lưu ý: điều này không hỗ trợ các biến môi trường đa dòng.
BenC

2
Trong trường hợp của tôi, iterating trên proc.stdout()sản lượng byte, do đó tôi đã nhận được một TypeErrortrên line.partition(). Chuyển đổi thành chuỗi với đã line.decode().partition("=")giải quyết vấn đề.
Sam F

1
Điều này rất hữu ích. Tôi thực hiện ['env', '-i', 'bash', '-c', 'source .bashrc && env']để cung cấp cho bản thân mình chỉ các biến môi trường được thiết lập bởi các tập tin rc
xaviersjs

32

Sử dụng dưa chua:

import os, pickle
# For clarity, I moved this string out of the command
source = 'source init_env'
dump = '/usr/bin/python -c "import os,pickle;print pickle.dumps(os.environ)"'
penv = os.popen('%s && %s' %(source,dump))
env = pickle.loads(penv.read())
os.environ = env

Đã cập nhật:

Điều này sử dụng json, subprocess và sử dụng / bin / bash một cách rõ ràng (để hỗ trợ ubuntu):

import os, subprocess as sp, json
source = 'source init_env'
dump = '/usr/bin/python -c "import os, json;print json.dumps(dict(os.environ))"'
pipe = sp.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=sp.PIPE)
env = json.loads(pipe.stdout.read())
os.environ = env

Cái này có một vấn đề trên Ubuntu - shell mặc định ở đó /bin/dash, nó không biết sourcelệnh. Để sử dụng nó trên Ubuntu, bạn phải chạy /bin/bashrõ ràng, ví dụ bằng cách sử dụng penv = subprocess.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=subprocess.PIPE).stdout(điều này sử dụng subprocessmô-đun mới hơn phải được nhập).
Martin Pecka

22

Thay vì có nguồn tập lệnh Python của bạn là tập lệnh bash, sẽ đơn giản và thanh lịch hơn nếu có nguồn tập lệnh trình bao bọc init_envvà sau đó chạy tập lệnh Python của bạn với môi trường đã sửa đổi.

#!/bin/bash
source init_env
/run/python/script.py

4
Nó có thể giải quyết vấn đề trong một số trường hợp, nhưng không phải tất cả chúng. Ví dụ: tôi đang viết một tập lệnh python cần thực hiện điều gì đó như tìm nguồn cung cấp tệp (thực sự nó tải các mô-đun nếu bạn biết tôi đang nói gì) và nó cần tải một mô-đun khác tùy thuộc vào một số trường hợp. Vì vậy, đây sẽ không giải quyết vấn đề của tôi ở tất cả
Davide

Điều này trả lời câu hỏi trong hầu hết các trường hợp và tôi sẽ sử dụng nó bất cứ khi nào có thể. Tôi đã gặp khó khăn khi thực hiện điều này trong IDE của mình cho một dự án nhất định. Một sửa đổi có thể có thể chạy toàn bộ điều trong một vỏ với môi trườngbash --rcfile init_env -c ./script.py
xaviersjs

6

Đã cập nhật câu trả lời của @ lesmana cho Python 3. Lưu ý việc sử dụng env -inó ngăn các biến môi trường không liên quan được thiết lập / đặt lại (có thể không chính xác do thiếu xử lý đối với các biến env đa dòng).

import os, subprocess
if os.path.isfile("init_env"):
    command = 'env -i sh -c "source init_env && env"'
    for line in subprocess.getoutput(command).split("\n"):
        key, value = line.split("=")
        os.environ[key]= value

Sử dụng điều này cho tôi "PATH: biến không xác định" vì env -i bỏ thiết lập đường dẫn. Nhưng nó hoạt động mà không có env -i. Cũng phải cẩn thận rằng dòng có thể có nhiều '='
Fujii

4

Ví dụ về câu trả lời xuất sắc của @ Brian trong một hàm:

import json
import subprocess

# returns a dictionary of the environment variables resulting from sourcing a file
def env_from_sourcing(file_to_source_path, include_unexported_variables=False):
    source = '%ssource %s' % ("set -a && " if include_unexported_variables else "", file_to_source_path)
    dump = '/usr/bin/python -c "import os, json; print json.dumps(dict(os.environ))"'
    pipe = subprocess.Popen(['/bin/bash', '-c', '%s && %s' % (source, dump)], stdout=subprocess.PIPE)
    return json.loads(pipe.stdout.read())

Tôi đang sử dụng chức năng tiện ích này để đọc thông tin đăng nhập aws và các tệp .env docker với include_unexported_variables=True.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.