2010-08-17 6 views
63

que tiene una secuencia de comandos que se ve algo como esto:Emulating Bash 'fuente' en Python

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

Cada vez que construyo corro 'init_env fuente' (donde init_env es el script de arriba) para establecer algunas variables .

Para lograr lo mismo en Python tenía este código que se ejecuta,

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) 

Pero entonces alguien decidió que sería bueno añadir una línea como la siguiente en el archivo init_env:

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

Obviamente, mi script de Python se vino abajo. Podría modificar la secuencia de comandos de Python para manejar esta línea, pero luego se romperá más tarde cuando alguien se le ocurra una nueva característica para usar en el archivo init_env.

La pregunta es si hay una manera fácil de ejecutar un comando Bash y dejar que modifique mi os.environ?

+0

relacionado: [Llamar al comando "origen" del subproceso.Popen] (http://stackoverflow.com/q/7040592/4279) – jfs

Respuesta

78

El problema con su enfoque es que está tratando de interpretar los scripts bash. Primero, intente interpretar la declaración de exportación. Entonces nota que las personas están usando expansión variable. Más tarde, las personas pondrán condicionales en sus archivos o procesarán sustituciones. Al final tendrás un intérprete de guión bash completo con un montón de bichos. No hagas eso.

Deje que Bash interprete el archivo por usted y luego recopile los resultados.

Puede hacerlo de esta manera:

#! /usr/bin/env python 

import os 
import pprint 
import subprocess 

command = ['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)) 

Asegúrese de que usted maneja errores en caso de golpe falla a source init_env, o fiesta en sí no se puede ejecutar, o subproceso no puede ejecutar bash, o cualquier otro error. Para obtener más información, lea la documentación en subprocess.

Nota: esto solo capturará las variables establecidas con la instrucción export, ya que env solo imprime las variables exportadas.

Disfrútalo.

Tenga en cuenta que el Python documentation indica que si desea manipular el entorno, debe manipular directamente os.environ en lugar de usar os.putenv(). Considero que es un error, pero estoy divagando.

+0

Bueno, esto era lo que estaba buscando. Gracias – getekha

+10

Si le importan las variables no exportadas y el script está fuera de su control, puede usar set -a para marcar todas las variables como exportadas. Simplemente cambie el comando a: ['bash', '-c', 'set -a && source init_env && env'] – ahal

+0

Tenga en cuenta que esto fallará en las funciones exportadas. Me encantaría que pudieras actualizar tu respuesta mostrando un análisis que también funcione para funciones. (por ejemplo, función fff() {echo "fff";}; export -f fff) –

14

En lugar de tener su secuencia de comandos de Python como fuente del script bash, sería más simple y más elegante tener un origen de secuencia de comandos del contenedor init_env y luego ejecutar el script de Python con el entorno modificado.

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

Esto resuelve el problema pero no responde la pregunta :) – getekha

+0

Puede resolver el problema en algunas circunstancias, pero no en todas. Por ejemplo, estoy escribiendo un script de python que necesita hacer algo * como * fuente del archivo (en realidad carga módulos si sabes de lo que estoy hablando), y necesita cargar un * módulo * diferente dependiendo de algunas circunstancias. Así que esto no resolvería mi problema en absoluto – Davide

21

Uso de la salmuera:

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 

Actualizado:

Esto utiliza JSON, subproceso, y explícitamente utiliza/bin/bash (para el apoyo 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 
+5

esto es inteligente. –

+0

de acuerdo, ¡esto es interesante! – Hamy

+0

Este tiene un problema en Ubuntu - el shell predeterminado es '/ bin/dash', que no conoce el comando' source'. Para usarlo en Ubuntu, debe ejecutar '/ bin/bash' explícitamente, p. usando 'penv = subprocess.Popen (['/ bin/bash', '-c', '% s &&% s'% (fuente, volcado)], stdout = subprocess.PIPE) .stdout' (esto usa el módulo 'subprocess' más nuevo que se debe importar). –

-8
execfile('filename') 

debe hacerlo

+5

No, realmente no debería. Execfile analiza el 'nombre de archivo' como python, que no es lo que preguntó el asker ... si vas a responder una pregunta de 4 años, realmente deberías agregar algo útil. – Foon

+0

Ah, he entendido mal su pregunta. Lo leí como una funcionalidad de "fuente similar" en Python. Estaba buscando esta función hoy y me pregunté por qué no estaba incluida, mi mal. – rupert160

1

Actualización de la respuesta de @ lesmana para Python 3. Observe el uso de env -i que evita que las variables de entorno externas se configuren/reinicien (potencialmente incorrectamente dada la falta de manejo de las variables env multilíneas).

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 
2

Ejemplo envolver @ excelente respuesta de Brian en una función:

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

estoy usando esta función de utilidad para leer AWS credenciales y la ventana acoplable .env archivos con include_unexported_variables=True.

Cuestiones relacionadas