web-dev-qa-db-fra.com

Émuler Bash 'source' dans Python

J'ai un script qui ressemble à ceci:

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

Chaque fois que je construis, j'exécute 'source init_env' (où init_env est le script ci-dessus) pour configurer certaines variables.

Pour accomplir la même chose dans Python j'avais ce code en cours d'exécution,

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)

Mais alors quelqu'un a décidé que ce serait bien d'ajouter une ligne comme la suivante au init_env fichier:

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

Évidemment, mon script Python s'est effondré. Je pourrais modifier le script Python pour gérer cette ligne, mais il se cassera plus tard quand quelqu'un propose une nouvelle fonctionnalité à utiliser dans le init_env fichier.

La question est de savoir s'il existe un moyen simple d'exécuter une commande Bash et de la laisser modifier mon os.environ?

79
getekha

Le problème avec votre approche est que vous essayez d'interpréter des scripts bash. Vous essayez d'abord d'interpréter la déclaration d'exportation. Ensuite, vous remarquez que les gens utilisent l'expansion variable. Plus tard, les gens mettront des conditions dans leurs fichiers ou traiteront les substitutions. À la fin, vous aurez un interpréteur de script bash complet avec un gazillion de bugs. Ne fais pas ça.

Laissez Bash interpréter le fichier pour vous, puis collectez les résultats.

Vous pouvez le faire comme ceci:

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

Assurez-vous de gérer les erreurs dans le cas où bash ne parvient pas à source init_env, Ou bash lui-même ne s'exécute pas, ou le sous-processus ne parvient pas à exécuter bash, ou toute autre erreur.

le env -i au début de la ligne de commande crée un environnement propre. cela signifie que vous n'obtiendrez que les variables d'environnement de init_env. si vous voulez l'environnement système hérité, omettez env -i.

Lisez la documentation sur sous-processus pour plus de détails.

Remarque: cela ne capturera que les variables définies avec l'instruction export, car env imprime uniquement les variables exportées.

Prendre plaisir.

Notez que la documentation Python indique que si vous voulez manipuler l'environnement, vous devez manipuler os.environ Directement au lieu d'utiliser os.putenv(). Je considère que c'est un bug, mais je m'égare.

96
lesmana

Utilisation de cornichon:

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

Mis à jour:

Cela utilise json, sous-processus, et utilise explicitement/bin/bash (pour le support 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
25
Brian

Plutôt que d'avoir votre Python le script bash, il serait plus simple et plus élégant d'avoir une source de script wrapper init_env puis exécutez votre Python avec l'environnement modifié.

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

Mise à jour de la réponse de @ lesmana pour Python 3. Remarquez l'utilisation de env -i qui empêche les variables d'environnement étrangères d'être définies/réinitialisées (potentiellement incorrectement étant donné le manque de gestion des variables env multilignes).

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
4
Al Johri

Exemple enveloppant l'excellente réponse de Brian dans une fonction:

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

J'utilise cette fonction utilitaire pour lire les informations d'identification aws et les fichiers docker .env avec include_unexported_variables=True.

3
JDiMatteo