Emulando Bash ‘source’ en Python

Tengo un guión que se parece a esto:

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

Cada vez que construyo ejecuto ‘source init_env’ (donde init_env es el script anterior) para configurar algunas variables.

Para lograr lo mismo en Python tuve este código en ejecución,

 reg = re.compile('export (?P\w+)(\=(?P.+))*') 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 agregar una línea como la siguiente al archivo init_env :

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

Obviamente mi script en Python se derrumbó. Podría modificar la secuencia de comandos de Python para manejar esta línea, pero luego se romperá más adelante cuando alguien presente 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 .

El problema con su enfoque es que está intentando interpretar los scripts de bash. Primero intenta interpretar la statement de exportación. Entonces te das cuenta de que las personas están utilizando la expansión variable. Las personas posteriores pondrán condicionales en sus archivos o procesarán sustituciones. Al final, tendrá un intérprete completo de scripts de bash con una gran cantidad de errores. No hagas eso

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

Puedes hacerlo así:

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

Asegúrese de que maneja los errores en caso de que bash falle en la source init_env , o que bash no se ejecute, o que el subproceso no ejecute bash, o cualquier otro error.

El env -i al principio de la línea de comando crea un entorno limpio. eso significa que solo obtendrás las variables de entorno de init_env . Si desea el entorno del sistema heredado, omita env -i .

Lea la documentación sobre el subproceso para más detalles.

Nota: esto solo capturará las variables establecidas con la statement de export , ya que env solo imprime las variables exportadas.

Disfrutar.

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

Utilizando pickle:

 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 usa json, subprocess, y usa explícitamente / bin / bash (para soporte de 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 

En lugar de tener su fuente de script de Python en el script de bash, sería más simple y elegante tener una fuente de script de envoltorio init_env y luego ejecutar su script de Python con el entorno modificado.

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

Se actualizó la respuesta de @ lesmana para Python 3. Observe el uso de env -i que evita que se configuren / reinicien variables de entorno extrañas (posiblemente de forma incorrecta debido a la falta de manejo de las variables env de multilínea).

 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 

Ejemplo que encierra la 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 las credenciales de aws y los archivos .env de la ventana acoplable con include_unexported_variables=True .

 execfile('filename') 

Deberías hacerlo