Python'da Bash 'kaynağını' taklit etme


93

Şuna benzeyen bir senaryom var:

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

Her derlediğimde, bazı değişkenleri ayarlamak için 'source init_env' (burada init_env yukarıdaki komut dosyasıdır) çalıştırıyorum.

Aynısını Python'da başarmak için bu kodu çalıştırdım,

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)

Ama sonra birisiinit_env dosyaya aşağıdaki gibi bir satır eklemenin iyi olacağına karar verdi :

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

Belli ki Python betiğim dağıldı. Python betiğini bu satırı işleyecek şekilde değiştirebilirim, ancak daha sonra birisiinit_env dosyada kullanmak için yeni bir özellik ortaya çıkardığında bu sadece kırılır .

Soru, Bash komutunu çalıştırmanın kolay bir yolu olup olmadığı ve benim os.environ?


related: Alt
işlemden

Yanıtlar:


110

Yaklaşımınızla ilgili sorun, bash komut dosyalarını yorumlamaya çalışmanızdır. Önce ihracat beyanını yorumlamaya çalışıyorsunuz. Sonra insanların değişken genişletme kullandığını fark edersiniz. Daha sonra insanlar dosyalarına koşul koyacaklar veya ikameleri işleyecekler. Sonunda, gazilyon hata içeren tam gelişmiş bir bash komut dosyası yorumlayıcısına sahip olacaksınız. Bunu yapma.

Bash'in dosyayı sizin için yorumlamasına ve ardından sonuçları toplamasına izin verin.

Bunu şu şekilde yapabilirsiniz:

#! /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'ın başarısız olması source init_envveya bash'ın kendisinin yürütmemesi veya alt işlemin bash'ı veya diğer hataları yürütmemesi durumunda hataları işlediğinizden emin olun .

env -iKomut satırının başına temiz bir ortam oluşturur. bu, yalnızca ortam değişkenlerini alacağınız anlamına gelir init_env. devralınan sistem ortamını istiyorsanız atlayın env -i.

Daha fazla ayrıntı için alt işlemle ilgili belgeleri okuyun .

Not: Bu, yalnızca dışa aktarılan değişkenleri yazdırdığı için yalnızca exportifadeyle ayarlanan değişkenleri yakalar env.

Zevk almak.

Python belgelerinin , ortamı değiştirmek istiyorsanız os.environ, kullanmak yerine doğrudan manipüle etmeniz gerektiğini söylediğini unutmayın os.putenv(). Bunu bir böcek olarak görüyorum, ama konudan sapıyorum.


12
Dışa aktarılmayan değişkenleri önemsiyorsanız ve komut dosyası kontrolünüzün dışındaysa, tüm değişkenleri dışa aktarılmış olarak işaretlemek için -a setini kullanabilirsiniz. Komutu şu şekilde değiştirin: ['bash', '-c', 'set -a && source init_env && env']
ahal

Bunun dışa aktarılan işlevlerde başarısız olacağını unutmayın. Yanıtınızı, işlevler için de işe yarayan ayrıştırmayı göstererek güncelleyebilirseniz çok sevinirim. (örn. function fff () {echo "fff";}; dışa aktar -f fff)
DA

2
Not: Bu, çok satırlı ortam değişkenlerini desteklemez.
BenC

2
Benim durumumda, proc.stdout()baytlar üzerinde yineleme yapmak , böylece bir TypeErroron alıyordum line.partition(). Dizgeye dönüştürmek line.decode().partition("=")sorunu çözdü.
Sam F

1
Bu çok yardımcı oldu. ['env', '-i', 'bash', '-c', 'source .bashrc && env']Kendime yalnızca rc dosyası tarafından ayarlanan ortam değişkenlerini vermek için çalıştırdım
xaviersjs

32

Turşu kullanmak:

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

Güncellenmiş:

Bu, json, subprocess kullanır ve açıkça / bin / bash kullanır (ubuntu desteği için):

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

Bunun Ubuntu'da bir sorunu var - varsayılan kabuk var /bin/dash, bu da sourcekomutu bilmiyor . Ubuntu'da kullanmak için, /bin/bashaçıkça çalıştırmalısınız , örneğin kullanarak penv = subprocess.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=subprocess.PIPE).stdout(bu, subprocessiçe aktarılması gereken daha yeni modülü kullanır ).
Martin Pecka

23

Python betiğinizin bash betiğini kaynağına sahip olmak yerine, bir sarmalayıcı komut dosyası kaynağına sahip olmak init_envve ardından Python betiğinizi değiştirilmiş ortamla çalıştırmak daha basit ve daha zarif olacaktır .

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

5
Sorunu bazı durumlarda çözebilir, ancak hepsini çözemez. Örneğin , dosyayı kaynak gibi bir şey yapması gereken bir python betiği yazıyorum (aslında neden bahsettiğimi biliyorsanız modülleri yükler) ve farklı bir bazı koşullara bağlı olarak modül . Yani bu benim sorunumu hiç çözmez
Davide

Bu, çoğu durumda soruyu yanıtlar ve mümkün olan her yerde onu kullanırdım. Belirli bir proje için bu işi IDE'mde yapmakta zorlandım. Olası bir değişiklik, her şeyi ortamla birlikte bir kabukta çalıştırmak olabilir.bash --rcfile init_env -c ./script.py
xaviersjs

6

Python 3 için @ lesmana'nın yanıtı güncellendi. Bunun kullanılmasının, env -iyabancı ortam değişkenlerinin ayarlanmasını / sıfırlanmasını engellediğine dikkat edin (potansiyel olarak yanlış, çok satırlı ortam değişkenlerinin işlenmemesi nedeniyle).

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

Bunu kullanmak bana "PATH: tanımsız değişken" verir çünkü env -i yolu geçersiz kılar. Ancak env -i olmadan çalışır. Ayrıca hat olabileceğini dikkatli olun birden '='
Fujii

5

Örnek @ Brian'ın bir işlevdeki mükemmel cevabı:

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 kimlik bilgilerini ve docker .env dosyalarını okumak için bu yardımcı program işlevini kullanıyorum include_unexported_variables=True.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.