จำลอง Bash 'source' ใน Python


92

ฉันมีสคริปต์ที่มีลักษณะดังนี้:

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

ทุกครั้งที่สร้างฉันจะเรียกใช้ 'source init_env' (โดยที่ init_env เป็นสคริปต์ด้านบน) เพื่อตั้งค่าตัวแปรบางตัว

เพื่อให้บรรลุสิ่งเดียวกันใน Python ฉันมีรหัสนี้ทำงานอยู่

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)

แต่แล้วมีคนตัดสินใจว่าจะเป็นการดีที่จะเพิ่มบรรทัดต่อไปนี้ลงในinit_envไฟล์:

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

เห็นได้ชัดว่าสคริปต์ Python ของฉันพังทลาย ฉันสามารถแก้ไขสคริปต์ Python เพื่อจัดการกับบรรทัดนี้ แต่มันจะพังในภายหลังเมื่อมีคนมาพร้อมกับคุณสมบัติใหม่ที่จะใช้ในinit_envไฟล์

คำถามคือถ้ามีวิธีง่ายๆในการรันคำสั่ง Bash และปล่อยให้มันแก้ไข my os.environ?


คำตอบ:


109

ปัญหาในแนวทางของคุณคือคุณกำลังพยายามตีความ bash scripts ก่อนอื่นคุณเพียงแค่พยายามตีความคำสั่งการส่งออก จากนั้นคุณสังเกตเห็นว่ามีคนใช้การขยายตัวแปร คนต่อมาจะใส่เงื่อนไขในไฟล์ของตนหรือดำเนินการแทน ในท้ายที่สุดคุณจะมีล่ามสคริปต์ทุบตีที่เต็มไปด้วยข้อบกพร่อง gazillion อย่าทำอย่างนั้น

ให้ Bash ตีความไฟล์ให้คุณแล้วรวบรวมผลลัพธ์

คุณสามารถทำได้ดังนี้:

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

ตรวจสอบให้แน่ใจว่าคุณจัดการข้อผิดพลาดในกรณีที่ bash ล้มเหลวsource init_envหรือ bash ไม่สามารถดำเนินการได้หรือกระบวนการย่อยล้มเหลวในการดำเนินการ bash หรือข้อผิดพลาดอื่น ๆ

env -iที่จุดเริ่มต้นของบรรทัดคำสั่งสร้างสภาพแวดล้อมที่สะอาด init_envนั่นหมายความว่าคุณจะได้รับตัวแปรสภาพแวดล้อมจาก env -iถ้าคุณต้องการสภาพแวดล้อมของระบบสืบทอดแล้วละเว้น

อ่านเอกสารเกี่ยวกับกระบวนการย่อยสำหรับรายละเอียดเพิ่มเติม

หมายเหตุ: สิ่งนี้จะจับเฉพาะตัวแปรที่ตั้งค่าด้วยexportคำสั่งเนื่องจากenvพิมพ์เฉพาะตัวแปรที่ส่งออกเท่านั้น

สนุก.

โปรดทราบว่าเอกสารหลามบอกว่าถ้าคุณต้องการที่จะจัดการกับสภาพแวดล้อมที่คุณควรจัดการโดยตรงแทนการใช้os.environ os.putenv()ฉันคิดว่าเป็นจุดบกพร่อง แต่ฉันพูดนอกเรื่อง


12
หากคุณสนใจเกี่ยวกับตัวแปรที่ไม่ได้ส่งออกและสคริปต์อยู่นอกการควบคุมของคุณคุณสามารถใช้ set -a เพื่อทำเครื่องหมายตัวแปรทั้งหมดเป็นส่งออก เพียงแค่เปลี่ยนคำสั่งเป็น: ['bash', '-c', 'set -a && source init_env && env']
ahal

โปรดทราบว่าสิ่งนี้จะล้มเหลวในฟังก์ชันที่ส่งออก ฉันจะชอบถ้าคุณสามารถอัปเดตคำตอบของคุณที่แสดงการแยกวิเคราะห์ที่ใช้ได้กับฟังก์ชันด้วย (เช่นฟังก์ชัน fff () {echo "fff";}; export -f fff)
DA

2
หมายเหตุ: สิ่งนี้ไม่สนับสนุนตัวแปรสภาพแวดล้อมแบบหลายบรรทัด
BenC

2
ในกรณีของฉัน iterating กว่าproc.stdout()อัตราผลตอบแทนไบต์ดังนั้นฉันถูกรับบนTypeError line.partition()การแปลงเป็นสตริงline.decode().partition("=")ช่วยแก้ปัญหาได้
Sam F

1
สิ่งนี้มีประโยชน์มาก ฉันดำเนินการ['env', '-i', 'bash', '-c', 'source .bashrc && env']เพื่อให้ตัวแปรสภาพแวดล้อมที่กำหนดโดยไฟล์ rc
xaviersjs

32

ใช้ดอง:

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

อัปเดต:

สิ่งนี้ใช้ json กระบวนการย่อยและการใช้ / bin / bash อย่างชัดเจน (สำหรับการรองรับ 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

อันนี้มีปัญหาใน Ubuntu - เชลล์เริ่มต้นมีอยู่/bin/dashซึ่งไม่ทราบsourceคำสั่ง ในการใช้งานบน Ubuntu คุณต้องเรียกใช้/bin/bashอย่างชัดเจนเช่นโดยใช้penv = subprocess.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=subprocess.PIPE).stdout(ซึ่งใช้subprocessโมดูลที่ใหม่กว่าซึ่งต้องนำเข้า)
Martin Pecka

22

แทนที่จะให้สคริปต์ Python ของคุณเป็นแหล่งที่มาของสคริปต์ bash มันจะง่ายกว่าและสวยงามกว่าที่จะมีซอร์สสคริปต์ wrapper init_envจากนั้นเรียกใช้สคริปต์ Python ของคุณด้วยสภาพแวดล้อมที่แก้ไข

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

5
อาจแก้ปัญหาได้ในบางสถานการณ์ แต่ไม่ใช่ทั้งหมด ยกตัวอย่างเช่นผมเขียนสคริปต์หลามที่ความต้องการที่จะทำบางสิ่งบางอย่างเช่นการจัดหาไฟล์ (ที่จริงมันโหลดโมดูลถ้าคุณรู้ว่าสิ่งที่ผมพูดถึง) และจะต้องมีการโหลดที่แตกต่างกันโมดูลขึ้นอยู่กับสถานการณ์บางอย่าง ดังนั้นสิ่งนี้จะไม่ช่วยแก้ปัญหาของฉันเลย
Davide

สิ่งนี้ตอบคำถามในกรณีส่วนใหญ่และฉันจะใช้ทุกที่ที่เป็นไปได้ ฉันมีช่วงเวลาที่ยากลำบากในการทำงานนี้ใน IDE ของฉันสำหรับโครงการหนึ่ง ๆ การปรับเปลี่ยนที่เป็นไปได้อย่างหนึ่งคือการเรียกใช้สิ่งทั้งหมดในเชลล์ด้วยสภาพแวดล้อมbash --rcfile init_env -c ./script.py
xaviersjs

6

คำตอบของ Python 3 ที่อัปเดตแล้วโปรดสังเกตการใช้งานenv -iที่ป้องกันไม่ให้มีการตั้งค่า / รีเซ็ตตัวแปรสภาพแวดล้อมภายนอก (อาจไม่ถูกต้องเนื่องจากขาดการจัดการสำหรับตัวแปร env หลายบรรทัด)

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

การใช้สิ่งนี้ทำให้ฉัน "PATH: ตัวแปรที่ไม่ได้กำหนด" เนื่องจาก env -i ยกเลิกการตั้งค่าเส้นทาง แต่มันทำงานได้โดยไม่ต้อง env -i ระวังด้วยว่าเส้นอาจมีหลาย '='
Fujii

5

ตัวอย่างการห่อคำตอบที่ยอดเยี่ยมของ Brian ในฟังก์ชัน:

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())

ฉันใช้ฟังก์ชั่นยูทิลิตี้นี้เพื่ออ่านข้อมูลประจำตัว aws และไฟล์include_unexported_variables=True. env ของนักเทียบท่าด้วย.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.