Appel de la commande "source" à partir du sous-processus.Popen

j'ai un .SH script que j'appelle avec source the_script.sh . L'appeler régulièrement est bien. Cependant, j'essaie de l'appeler à partir de mon script python, par subprocess.Popen .

L'appelant de Popen, je reçois les erreurs suivantes dans les deux scénarios suivants appels:

foo = subprocess.Popen("source the_script.sh")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 672, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1213, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory


>>> foo = subprocess.Popen("source the_script.sh", shell = True)
>>> /bin/sh: source: not found

qu'est-ce qui se passe? Pourquoi ne puis-je pas appeler "source" de Popen, alors que je peux en dehors de python?

24
demandé sur Mogsdad 2011-08-12 17:13:11

7 réponses

source n'est pas une commande exécutable, c'est un builtin de shell.

le cas le plus courant pour utiliser source est d'exécuter un script shell qui change l'environnement et de conserver cet environnement dans le shell actuel. C'est exactement comme cela que virtualenv fonctionne pour modifier l'environnement python par défaut.

créer un sous-processus et utiliser source dans le sous-processus ne fera probablement rien d'utile, il ne modifiera pas l'environnement du processus parent, aucun des effets secondaires de l'utilisation du script source n'aura lieu.

Python a une commande analogue, execfile , qui exécute le fichier spécifié en utilisant l'espace de noms global python courant (ou un autre, si vous en fournissez un), que vous pourriez utiliser de la même manière que la commande bash source .

19
répondu SingleNegationElimination 2011-08-12 13:18:52

vous pouvez simplement exécuter la commande dans un sous-puits et utiliser les résultats pour mettre à jour l'environnement actuel.

def shell_source(script):
    """Sometime you want to emulate the action of "source" in bash,
    settings some environment variables. Here is a way to do it."""
    import subprocess, os
    pipe = subprocess.Popen(". %s; env" % script, stdout=subprocess.PIPE, shell=True)
    output = pipe.communicate()[0]
    env = dict((line.split("=", 1) for line in output.splitlines()))
    os.environ.update(env)
27
répondu xApple 2012-10-03 12:24:11

Broken Popen("source the_script.sh") est l'équivalent de Popen(["source the_script.sh"]) qui tente sans succès de lancer le programme 'source the_script.sh' . Il ne peut pas le trouver, d'où l'erreur "No such file or directory" .

Cassé Popen("source the_script.sh", shell=True) échoue parce que source est un bash builtin de commande (de type help source en bash) mais le shell par défaut est /bin/sh qui ne les comprend pas ( /bin/sh utilise . ). En supposant qu'il pourrait y avoir d'autres bash-ismes dans the_script.sh , il devrait être exécuté en utilisant bash:

foo = Popen("source the_script.sh", shell=True, executable="/bin/bash")

comme @IfLoop a dit , il n'est pas très utile d'exécuter source dans un sous-processus parce qu'il ne peut pas affecter l'environnement du parent.

os.environ.update(env) - méthodes basées échouer si the_script.sh exécute unset pour certaines variables. os.environ.clear() pourrait être appelé pour réinitialiser l'environnement:

#!/usr/bin/env python
import os
from pprint import pprint
from subprocess import check_output

os.environ['a'] = 'a'*100
# POSIX: name shall not contain '=', value doesn't contain '"151910920"'
output = check_output("source the_script.sh; env -0",   shell=True,
                      executable="/bin/bash")
# replace env
os.environ.clear() 
os.environ.update(line.partition('=')[::2] for line in output.split('"151910920"'))
pprint(dict(os.environ)) #NOTE: only `export`ed envvars here

il utilise env -0 et .split('" 1519200920 "') suggéré par @unutbu

pour supporter les octets arbitraires dans os.environb , json module pourrait être utilisé (en supposant que nous utilisons la version Python où " JSON.décharges non analysables par json.charges "issue est fixe):

pour éviter de passer l'environnement via pipes, le code Python peut être modifié pour s'invoquer dans l'environnement du sous-processus par exemple:

#!/usr/bin/env python
import os
import sys
from pipes import quote
from pprint import pprint

if "--child" in sys.argv: # executed in the child environment
    pprint(dict(os.environ))
else:
    python, script = quote(sys.executable), quote(sys.argv[0])
    os.execl("/bin/bash", "/bin/bash", "-c",
        "source the_script.sh; %s %s --child" % (python, script))
11
répondu jfs 2017-05-23 12:17:41

source est un shell bash spécifique intégré (et les coques non-interactives sont souvent des coques légères au lieu de bash). Au lieu de cela, il suffit d'appeler /bin/sh :

foo = subprocess.Popen(["/bin/sh", "the_script.sh"])
2
répondu phihag 2011-08-12 13:15:46

une variation sur la réponse de @xApple car il est parfois utile de pouvoir source un script shell (plutôt qu'un fichier Python) pour définir des variables d'environnement, et peut-être effectuer d'autres opérations shell, et ensuite propager cet environnement à l'interpréteur Python plutôt que de perdre cette information lorsque la sous-couche ferme.

la raison d'une variation est que l'hypothèse d'un format de sortie à une variable par ligne de "env" n'est pas 100% robuste: j'ai juste eu à traiter avec une variable (une fonction shell, je pense) contenant une newline, qui a foiré l'analyse. Voici donc une version légèrement plus complexe, qui utilise Python lui-même pour formater le dictionnaire d'environnement d'une manière robuste:

import subprocess
pipe = subprocess.Popen(". ./shellscript.sh; python -c 'import os; print \"newenv = %r\" % os.environ'", 
    stdout=subprocess.PIPE, shell=True)
exec(pipe.communicate()[0])
os.environ.update(newenv)

Peut-être qu'il est plus lisible? Cela permet également de s'assurer que l'analyse de l'environnement n'est pas faussée si quelqu'un met une déclaration echo dans le script qui est fourni. Bien sûr, il y a un exec ici, alors faites attention à ceux qui n'ont pas confiance. entrée... mais je pense que c'est implicite dans une discussion sur la façon de source/exécuter un script shell arbitraire; -)

mise à JOUR: voir @unutbu commentaire de @xApple réponse pour une alternative (probablement plus agréable) de manière à gérer les retours à la ligne dans le "151910920 de sortie".

1
répondu andybuckley 2017-05-23 12:17:41

si vous voulez appliquer la commande source à d'Autres scripts ou exécutables, alors vous pouvez créer un autre fichier de script d'enroulement et appeler la commande" source " à partir de celui-ci avec toute autre logique dont vous avez besoin. Dans ce cas, cette commande source modifiera le contexte local où elle s'exécute - à savoir dans le sous-processus que ce sous-processus.Popen crée.

cela ne fonctionnera pas si vous avez besoin de modifier le contexte python, où votre programme est en cours d'exécution.

0
répondu Vlad Ogay 2012-12-27 10:33:01

il semble qu'il y ait beaucoup de réponses à cela, qu'ils n'ont pas tous lu, donc ils l'ont peut-être déjà signalé; mais, en appelant des commandes shell comme celle-ci, vous devez passer shell=True à L'appel Popen. Sinon, vous pouvez appeler Popen (shlex.Split.))( assurez-vous d'importer shlex.

j'utilise cette fonction pour trouver un fichier et modifier l'environnement actuel.

def set_env(env_file):
    while True:
        source_file = '/tmp/regr.source.%d'%random.randint(0, (2**32)-1)
        if not os.path.isfile(source_file): break
    with open(source_file, 'w') as src_file:
        src_file.write('#!/bin/bash\n')
        src_file.write('source %s\n'%env_file)
        src_file.write('env\n')
    os.chmod(source_file, 0755)
    p = subprocess.Popen(source_file, shell=True,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = p.communicate()
    setting = re.compile('^(?P<setting>[^=]*)=')
    value = re.compile('=(?P<value>.*$)')
    env_dict = {}
    for line in out.splitlines():
        if setting.search(line) and value.search(line):
            env_dict[setting.search(line).group('setting')] = value.search(line).group('value')
    for k, v in env_dict.items():
        os.environ[k] = v
    for k, v in env_dict.items():
        try:
            assert(os.getenv(k) == v)
        except AssertionError:
            raise Exception('Unable to modify environment')
0
répondu user1905107 2013-12-30 00:08:29