IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

shell function et subprocess


Sujet :

Python

  1. #1
    Membre averti
    Inscrit en
    Mai 2006
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Mai 2006
    Messages : 24
    Par défaut shell function et subprocess
    bonjour,
    Par avance désolé pour la longueur de cette question, mais je tourne en rond sur ce problème depuis 3 semaines. J'ai essayé de faire un exemple simple.
    Je suis sous linux et je suis dépendant d'un environement de dev que je set via des bash fournis par une infra.
    Pour la suite, considérons que le fichier tar joint est décompressé dans /tmp (tar -C /tmp/ -xvf env.tar.gz)

    env.tar.gz


    je veux scripter via python la séquence suivante

    ~$ . /tmp/tools/setEnv
    ~$ activateEnv v1
    ~$ rootCmd.sh
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /tmp/tools/envs/cmds/v1/convert-1.0.3.sh -u http://www.operation.com/api/
    convert : trying reaching http://www.operation.com/api/

    ~$ activateEnv v2
    ~$ rootCmd.sh
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /tmp/tools/envs/cmds/v2/inspect-1.4.2.sh -u http://www.audit.com/api/
    inspect : trying reaching http://www.audit.com/api/


    mon soucis, c'est que setEnv definis des fonctions shell (ici activateEnv), et positionne des variables.
    Pour les variables j'ai pu régler le problème en passant par une fonction python qui après chaque call de commande récupère les variables et vient les setter dans l'environement python.
    cf /tmp/python/utils.py
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def call_cmd(cmd):
      with tempfile.NamedTemporaryFile() as f:
        call = cmd + ' && env>' + f.name    
        out = (subprocess.Popen(call, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8'))
        for e in f.read().splitlines():
          e = e.decode('utf-8').strip().split('=')
          os.environ[e[0]] = e[1]
      return out
    si je scripte la séquence initiale, j'obtiens une erreur car la fonction shell activateEnv n'est pas trouvée

    ~$ python3 /tmp/python/script_KO.py
    ***** ENV V1 *****
    /bin/sh: activateEnv: command not found
    /tmp/tools/envs/cmds/common/rootCmd.sh: line 4: /-.sh: No such file or directory
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /-.sh -u

    ***** ENV V2 *****
    /bin/sh: activateEnv: command not found
    /tmp/tools/envs/cmds/common/rootCmd.sh: line 4: /-.sh: No such file or directory
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /-.sh -u

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    utils.call_cmd('. /tmp/tools/setEnv')
     
    print ('***** ENV V1 *****')
    utils.call_cmd('activateEnv v1')
    o = utils.call_cmd('rootCmd.sh')
    print(o)
     
    print ('***** ENV V2 *****')
    utils.call_cmd('activateEnv v2')
    o = utils.call_cmd('rootCmd.sh')
    print(o)
    pour que ça fonctionne, il faut que j'appelle setEnv avant chaque activateEnv

    ~$ python3 /tmp/python/script_OK.py
    ***** ENV V1 *****
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /tmp/tools/envs/cmds/v1/convert-1.0.3.sh -u http://www.operation.com/api/
    convert : trying reaching http://www.operation.com/api/

    ***** ENV V2 *****
    runing rootCmd : /tmp/tools/envs/cmds/common/rootCmd.sh
    calling subCmd : /tmp/tools/envs/cmds/v2/inspect-1.4.2.sh -u http://www.audit.com/api/
    inspect : trying reaching http://www.audit.com/api/

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    print ('***** ENV V1 *****')
    utils.call_cmd('. /tmp/tools/setEnv && activateEnv v1')
    o = utils.call_cmd('rootCmd.sh')
    print(o)
     
    print ('***** ENV V2 *****')
    utils.call_cmd('. /tmp/tools/setEnv && activateEnv v2')
    o = utils.call_cmd('rootCmd.sh')
    print(o)
    Dans mon exemple ça n'a pas d'impact. En revanche dans le vrai cas d'usage, setEnv est très très coûteux.
    Je précise que la solution ne peux pas passer par une modification des shells dans /tmp/tools. Dans mon cas cette partie est fournie et non modifiable.

    D'avance merci pour votre aide

  2. #2
    Membre prolifique
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 815
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 815
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par crashXpert Voir le message
    mon soucis, c'est que setEnv definis des fonctions shell (ici activateEnv), et positionne des variables.
    Pour les variables j'ai pu régler le problème en passant par une fonction python qui après chaque call de commande récupère les variables et vient les setter dans l'environement python.
    Si j'ai bien compris, tu voudrais que ton script Python puisse appeler la fonction shell "activateEnv", fonction créée dans le progamme "setEnv" et appelé avant que Python ne soit lancé.
    Je suis désolé, ce n'est pas possible.

    Sous Unix/Linux, il y a un grand principe: indépendance des processus. Chaque programme que tu appelles (ls, vi, rm, cp, cat, echo, ...) génère un processus, un environnement dans lequel le programme va travailler. Ce processus, qui possède sa propre mémoire, sa propre gestion de périphériques, est totalement indépendant et isolé du shell dans lequel toi tu te trouves quand tu appelles le programme. On appelle cet environnement un "fils" (le fils de l'environnement dans lequel toi tu te trouves)

    Or, contrairement aux variables du shell (ex HOME, LOGNAME, PATH) les fonctions qui sont dans ton environnement à toi ne sont pas exportables à tes fils. Tu aurais dit "j'ai une variable toto j'aimerais que Python la connaisse" ok ça aurait pu se faire => import os; print(os.environ["toto"]) à condition que "toto" ait été exportée (rôle de la commande "export" du bash qui permet à une variable d'être automatiquement transmise à chaque fils à sa création).
    Mais impossible à Python (qui, comme tous les autres programmes Unix, tourne lui-aussi dans un processus indépendant) de connaitre les fonctions de ton environnement. Donc Python ne connaissant pas "activateEnv", ses propres fils (qui sont les sous-processs créés par call_cmd()) ne la connaissent pas non plus.

    Désolé.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  3. #3
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 696
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 696
    Par défaut
    Salut,

    La suite de commandes bash:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    . /tmp/tools/setEnv
    activateEnv v1
    rootCmd.sh
    devrait pouvoir s'exécuter avec un seul appel à subprocess. Un truc genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    commands = '''./tmp/tools/setEnv
    activateEnv v1
    rootCmd.sh'''
    subprocess.run(commands, shell=True,...)
    note: je n'ai pas essayé/testé.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  4. #4
    Membre prolifique
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 815
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 815
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    commands = '''./tmp/tools/setEnv
    activateEnv v1
    rootCmd.sh'''
    subprocess.run(commands, shell=True,...)
    Attention, ce n'est pas "./tmp/tools/setEnv" mais ". <espace> /tmp/tools/setEnv", ls ". <espace>" étant une syntaxe signifiant "exécute le script dans le shell courant et non dans son fils" et qu'on voit souvent dans des commandes telles que . .profile. Ensuite cette syntaxe peu lisible a été remplacée par source dans les shells plus récents => source /tmp/tools/setEnv.

    Citation Envoyé par wiztricks Voir le message
    note: je n'ai pas essayé/testé.
    Je l'ai fait mais ça n'a pas été une réussite
    1) un source "fct.sh" contenant
    Code bash : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #!/bin/bash
     
    fct() {
    	echo ok
    }

    Je charge le script via source fct.sh (ou . fct.sh) et je peux appeler fct => j'obtiens "ok" à l'écran.

    2) un script Python contenant
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #!/usr/bin/env python
     
    import subprocess
    subprocess.Popen("source fct.sh; fct", shell=True)
    Ben que dalle, "source" n'est pas connu. Remarque le message d'erreur vient de /bin/sh ("source" est une commande bash) mais même avec subprocess.Popen(". fct.sh; fct", shell=True) ça ne change rien.
    Ensuite on peut peut-être essayer voir du côté du paramètre "env" de Popen() mais là ça dépasse mes connaissances (et qqchose me dit que c'est limité aux seules variables). Mais même si on y arrive de cette façon, le PO ne souhaite pas appeler setEnv à chaque fois (trop lourd), il aimerait l'appeler une fois avant d'appeler Python et ça...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  5. #5
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 512
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 512
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    1) un source "fct.sh" contenant
    Code bash : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #!/bin/bash
     
    fct() {
    	echo ok
    }

    Je charge le script via source fct.sh (ou . fct.sh) et je peux appeler fct => j'obtiens "ok" à l'écran.

    2) un script Python contenant
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #!/usr/bin/env python
     
    import subprocess
    subprocess.Popen("source fct.sh; fct", shell=True)
    Ben que dalle, "source" n'est pas connu.
    subprocess.Popen(["/bin/bash", "-c", "source fct.sh; fct"]) marche et affiche "ok".

    Par contre, effectivement, comme les processus sont indépendants, les appels suivants à Bash ne connaîtront pas la fonction fct sans la recharger : subprocess.Popen(["/bin/bash", "-c", "fct"]) ne marchera pas.

    Remarque pour tout le monde : en Python, pour le module subprocess, sur Linux hors Android, shell=True revient à remplacer le premier argument (la commande) args par ["/bin/sh", "-c", args] ou ["/bin/sh", "-c"] + list(args) : https://github.com/python/cpython/bl...py#L1693-L1707

    Or, sur les distributions Linux que je connais, sh n'est pas un alias de Bash. Par exemple, sur Ubuntu, depuis un certain temps, sh est un alias de Dash : https://wiki.ubuntu.com/DashAsBinSh

    Si vous voulez exécuter du Bash, il faut oublier shell=True et passer directement en premier argument quelque chose de la forme ["/bin/bash", bashScriptFilePath] ou ["/bin/bash", "-c", bashCodeString] ou ["/bin/bash", "-ceou", "pipefail", bashCodeString].

  6. #6
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 341
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 341
    Par défaut
    En fait le soucis ici, c'est de savoir comment il veut passer les différente commandes au shell:
    -En une seul fois (donc là , c'est simple)
    -De façon fragmenter, c'est à dire:
    -une commande shell
    -python fait des actions
    -une autre commande
    -python
    ...
    Et dans ce cas, c'est plus compliqué, car pour ne pas à avoir a rejouer le setEnv à chaque fois, il faudra créer un process bash qui communique avec le python en permanence, or subprocess ou autre n'implémente pas cette feature.
    si on passe par popen (ou run), on ne peut faire qu'un seul envoi de commande car le stdin du process bash est fermé dans la foulée et donc on ne peut faire un autre envoi.
    Donc l'astuce, serait que python lance un bash en arrière plan et communique avec lui via des pipe nommé.

    Mais avant de sortir l'artillerie lourde, faudrait connaitre vraiment le besoin.

  7. #7
    Membre averti
    Inscrit en
    Mai 2006
    Messages
    24
    Détails du profil
    Informations forums :
    Inscription : Mai 2006
    Messages : 24
    Par défaut
    Désolé pour ma reaction tardive, je rentre de congés (et je ne pensais pas que les réponses seraient aussi rapides)
    Grand merci pour vos réponses.

    Merci disedorgue d'avoir su résumer mon problème en 5 lignes.
    C'est exactement ça:
    Citation Envoyé par disedorgue Voir le message
    -De façon fragmenter, c'est à dire:
    -une commande shell
    -python fait des actions
    -une autre commande
    -python
    ...
    Donc sur ce patern et en reprenant l'exemple que j'ai fournis, ça donnerait le scripting via python de la séquence
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     . /tmp/tools/setEnv
    [shell] activateEnv v1 
    [shell] rootCmd.sh
    (/tmp/tools/envs/cmds/common/rootCmd.sh sait retrouver et exécuter /tmp/tools/envs/cmds/v1/convert-1.0.3.sh via le setenv de activateEnv v1)
    [python] some python commands
    [shell] activateEnv v2 
    [shell] rootCmd.sh
    (/tmp/tools/envs/cmds/common/rootCmd.sh sait retrouver et exécuter /tmp/tools/envs/cmds/v2/inspect-1.4.2.sh  via le setenv de activateEnv v2)
    [python] some python commands
    Merci wiztricks et sve@r pour les détails sur les subprocess. J'avais découvert ces comportements empiriquement (sourcing, export), c'est maintenant plus clair. Avec le pattern de ma fonction python [CODE=python]call_cmd[CODE] je contourne le problème des variables d'env (dans mon use case réel, les scripts fournis par mon infra font de l'export).

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def call_cmd(cmd):
      with tempfile.NamedTemporaryFile() as f:
        call = cmd + ' && env>' + f.name    
        out = (subprocess.Popen(call, shell=True, stdout=subprocess.PIPE).stdout.read().decode('utf-8'))
        for e in f.read().splitlines():
          e = e.decode('utf-8').strip().split('=')
          os.environ[e[0]] = e[1]
      return out

    Comme mentionné dans mon message initial, si je préfixe toute mes commandes shell lancées depuis python par . /tmp/tools/setEnv && alors je n'ai plus de soucis avec les fonctions shell non trouvées.
    Mais je confirme que dans mon UC réel, l'équivalent du call à . /tmp/tools/setEnv ne doit se faire qu'une fois car extrêmement couteux, mais indispensable pour executer les commandes shells suivantes.

    Du coup, comme vérifié par pyramidev, l'export des fonctions shell n'existent pas et il n'y a pas vraiment de solution.

    L'idée de disedorgue me conviendrait.
    Avoir une sorte de singleton dans python qui représente une sorte de sandbox bash sur laquelle je peux exécuter mes commandes et récupérer les sorties standard (err, out, returncode). Pas de soucis si elle doive persister sur toute la durée de vie de mon process python.

    Auriez-vous un pattern à proposer, ou un tuto/doc à me pointer SVP ?

    Encore un immense merci pour votre temps.

  8. #8
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 341
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 341
    Par défaut
    Voici un poc largement améliorable, dans le même shell, il fait un ls -l, il défini une variable et ensuite il demande la valeur de cette variable et chacune des commandes sont envoyer par le python après la fin de la précédente.
    Le gros défaut, c'est que ici, on ne récupère pas directement le status de la commande shell qui a été envoyée, juste son stdout.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    import subprocess
    import os
     
    read_path = "/tmp/pipe.in"
    write_path = "/tmp/pipe.out"
     
    if os.path.exists(read_path):
        os.remove(read_path)
    if os.path.exists(write_path):
        os.remove(write_path)
     
    os.mkfifo(write_path)
    os.mkfifo(read_path)
     
    myPopen = subprocess.Popen(['bash', '-i'], stdin = os.open(write_path,os.O_RDONLY | os.O_NONBLOCK) , stdout = os.open(read_path,os.O_NONBLOCK | os.O_RDWR),) 
    wf = os.open(write_path, os.O_RDWR)
    os.write(wf,'ls -l\n'.encode())
     
    fifo=os.open(read_path,os.O_RDONLY)
     
    while True:
        data = os.read(fifo,1024)
        print('{0}'.format(data.decode()),end='')
        if len(data) < 1024:
            break
     
    os.write(wf,'FOOBAR=valeur1\n'.encode())
    print('On a envoyé une commande sans retour stdout, donc on ne lit surtout pas fifo')
     
    os.write(wf,'echo la valeur de FOOBAR est $FOOBAR\n'.encode())
    while True:
        data = os.read(fifo,1024)
        print('{0}'.format(data.decode()),end='')
        if len(data) < 1024:
            break
     
    os.close(wf)
    os.close(fifo)
    myPopen.terminate()

Discussions similaires

  1. [Python 3.X] accent et caractères spéciaux shell subprocess
    Par Rizcolas62 dans le forum Général Python
    Réponses: 7
    Dernier message: 04/11/2020, 18h51
  2. [Python 3.X] subprocess, retour shell, fin de ligne incomplète : résolution trop faible
    Par y0han dans le forum Général Python
    Réponses: 1
    Dernier message: 14/06/2015, 17h28
  3. Réponses: 9
    Dernier message: 22/11/2011, 11h26
  4. debian squeeze shell function
    Par slefevre01 dans le forum Shell et commandes GNU
    Réponses: 5
    Dernier message: 02/09/2011, 08h18
  5. Function Shell avec Runtime Access 2003
    Par gridin dans le forum Runtime
    Réponses: 5
    Dernier message: 16/11/2007, 22h24

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo