Contenu | Rechercher | Menus

Annonce

Si vous rencontrez des soucis à rester connecté sur le forum (ou si vous avez perdu votre mot de passe) déconnectez-vous et reconnectez-vous depuis cette page, en cochant la case "Me connecter automatiquement lors de mes prochaines visites".

#1 Le 25/08/2009, à 22:36

Totor

[Résolu] Python et les pipes

Bonsoir,

Comme indiqué dans un précédant post je m'initie à Python.
Pour ce faire, j'ai un "petit" projet personnel dont voici l'une des fonctionnalités (la plus capitale à vrai dire) :
Depuis python, exécuter unitairement et « contrôler » des instructions bash (le contrôle se limitant à la récupération de la valeur d'une variable)

En gros, j'aimerai avoir un processus bash en parallèle de mon processus Python et lui envoyer des instructions entre 2 lignes de code Python. L'environnement bash doit être conservé entre 2 lignes Python.

Pour cela j'utilise des pipes afin de communiquer entre les 2 environnements.

L'env. Bash est constitué d'un petit script « serveur » à l'écoute des demandes de l'environnement Python.

Voici le script (pipe.sh) :

#!/bin/bash
# set -x
py_fichierLog="/tmp/pipe.$$.log"
py_fichierErr="/tmp/pipe.$$.err"
py_fichierTrc="/tmp/pipe.$$.trc"
exec 1> >(tee -a "${py_fichierTrc}" )
exec 2> >(tee -a "${py_fichierErr}"  >&2)
exec 3>>"${py_fichierLog}"

py_pipeIN="$1"
py_pipeOUT="$2"
cat <<EOF >&3
$(date)
Nom du pipe entrant: ${py_pipeIN}
Nom du pipe sortant: ${py_pipeOUT}
EOF

[ ! -p "${py_pipeIN}" ] && mkfifo "${py_pipeIN}"
[ ! -p "${py_pipeOUT}" ] && mkfifo "${py_pipeOUT}"


py_fin=1
while [ ${py_fin} -eq 1 ]
do    
    read py_cmd < "${py_pipeIN}"
    echo "Recieved command : ${py_cmd}" >&3
    
    py_cmd="${py_cmd:-END}"
    [ "${py_cmd}" = "END" ] 
    py_fin=$?
    if [ ${py_fin} -eq 1 ] ; then
        if [[ "${py_cmd}" =~ ^GET:.+$ ]]; then
            py_variable="${py_cmd#GET:}"
            echo "Output value of ${py_variable} ---> ${!py_variable}" >&3
            echo ${!py_variable} >> "${py_pipeOUT}"
        elif [[ "${py_cmd}" =~ ^GETA:.+$ ]]; then
            py_variable="${py_cmd#GETA:}"
            echo "Output value of ${py_variable} ---> $(eval echo "\${${py_variable}[@]}")" >&3
            eval echo "\${${py_variable}[@]}" > "${py_pipeOUT}"
        else 
            echo -n "Evaluating '${py_cmd}' - return status : " >&3
            eval "${py_cmd}" 
            echo "$?">&3
    fi
  fi
done

[  -p "${py_pipeIN}" ] && rm -f "${py_pipeIN}"
[  -p "${py_pipeOUT}" ] && rm -f "${py_pipeOUT}"
 
echo "End." >&3
exit

Le script Python :

#!/usr/bin/python3.0
import subprocess
import os
import sys

nomFichier='/tmp/' + sys.argv[0] .rstrip("py") + str(os.getpid() )
nomPipeOUT=nomFichier + '.out'
nomPipeIN=nomFichier +'.in'

# lancement process bash
p=subprocess.Popen( "./pipe.sh " +  nomPipeOUT  + ' ' + nomPipeIN,  shell=True)
    
# Attente que le process bash ait créé les 2 pipes
while not os.path.exists(nomPipeOUT) or not os.path.exists(nomPipeIN):
    None

# Ouverture de pipe en écriture
pipeOUT=os.open(nomPipeOUT, os.O_WRONLY)
   
# envoi des données
os.write(pipeOUT,'((TOTO++));\n'.encode())
os.write(pipeOUT,'((TOTO++));\n'.encode())
os.write(pipeOUT,'((TOTO++));\n'.encode())
os.write(pipeOUT,'TABLEAU=( a b c );\n'.encode())
os.write(pipeOUT,'echo "(bash)TOTO=${TOTO}"\n'.encode())

# on demande la valeur de la variable TOTO
os.write(pipeOUT,'GET:TOTO\n'.encode())

# Ouverture du pipe en lecture 
pipeIN=os.open(nomPipeIN, os.O_RDONLY)
# récupération et affichage de la valeur
print('(Python) TOTO=' + os.read(pipeIN, 256).decode())
#os.close(pipeIN)

os.write(pipeOUT,'GETA:TABLEAU\n'.encode())
#pipeIN=os.open(nomPipeIN, os.O_RDONLY)

print('(Python) TABLEAU=' + os.read(pipeIN, 256).decode())

# fin du process bash
os.write(pipeOUT,'END\n'.encode())
    
print("FIN du test")

Mon problème étant celui-ci :
Il arrive parfois qu'il y ait un « Broken pipe » :

$./test.py                                                                                                                                                     
(bash)TOTO=3
(Python) TOTO=3
FIN du test

Traceback (most recent call last):

  File "./test.py", line 35, in <module>

    os.write(pipeOUT,'END\n'.encode())

OSError: [Errno 32] Broken pipe

La question est : pourquoi ? Un processus s'exécute plus rapidement que l'autre ?

La version de python utilisée :

$python --version
Python 3.0.1+

Autre chose :
Je n'ai pas réussi à créer les pipes nommés depuis Python. Lorsque je procédais ainsi, je n'arrivais qu'à obtenir qu'une seule instruction dans mon environnement bash. Dès que je la lisais, le fichier « pipe » était supprimait...
j'ai essayé avec les os.pipe(), os.open(), open(), os.Popen() en précisant les stdin, stdout... mais c'est la seule façon que j'ai trouvé pour arriver à mon but.
(c'est ce qui justifie la boucle d'attente de la création des 2 pipes par l'environnement bash)

donc, toutes les remarques (solutions) sur le principe sont bonnes à prendre pour moi

L'objectif de ce script python est un test de faisabilité, la gestion des erreurs est donc volontairement absente.

Merci !

EDIT :
- Si j'effectue 2 fois une demande de valeur, j'obtiens en permanance l'erreur "OSError: [Errno 32] Broken pipe"
Je dois fermer le pipe de lecture via Python puis le ré-ouvrir pour que cela fonctionne de temps à autre ... hmm
c.a.d les lignes os.close(pipeIN) et pipeIN=os.open(nomPipeIN, os.O_RDONLY) (qui sont actuellement en commentaires)
une idée ?

- j'ai modifié les sources, donc les n° de lignes du "Traceback" fourni ci-dessus ne sont lus valables mais il reste d'actualité.

Dernière modification par Totor (Le 26/08/2009, à 22:45)


-- Lucid Lynx --

Hors ligne

#2 Le 26/08/2009, à 01:48

ADcomp

Re : [Résolu] Python et les pipes

Totor a écrit :

Autre chose :
Je n'ai pas réussi à créer les pipes nommés depuis Python. Lorsque je procédais ainsi, je n'arrivais qu'à obtenir qu'une seule instruction dans mon environnement bash. Dès que je la lisais, le fichier « pipe » était supprimait...
j'ai essayé avec les os.pipe(), os.open(), open(), os.Popen() en précisant les stdin, stdout... mais c'est la seule façon que j'ai trouvé pour arriver à mon but.
(c'est ce qui justifie la boucle d'attente de la création des 2 pipes par l'environnement bash).

Utilise Popen du module "subprocess" .. http://docs.python.org/library/subprocess.html

his module intends to replace several other, older modules and functions, such as:

os.system
os.spawn*
os.popen*
popen2.*
commands.*


David [aka] ADcomp

Hors ligne

#3 Le 26/08/2009, à 10:02

Totor

Re : [Résolu] Python et les pipes

Bonjour,

Oui, c'est la 1ère chose que j'ai fait. Mais je n'ai pas réussi. C'est d'ailleurs ce que j'ai mentionné mais avec une erreur (désolé) :

Totor a écrit :

[...], os.Popen() en précisant les stdin, stdout... mais c'est la seule façon que j'ai trouvé pour arriver à mon but.

C'est subprocess.Popen et non os.Popen ...

Je vais tout de même m'y repencher...
En attendant, d'autres pistes ?


-- Lucid Lynx --

Hors ligne

#4 Le 26/08/2009, à 13:13

Totor

Re : [Résolu] Python et les pipes

Bon voilà, le résultat de mes tests avec subprocess.Popen :

Le script Python :

#!/usr/bin/python3.0
import subprocess
import os
import sys

nomFichier='/tmp/' + sys.argv[0] .rstrip("py") + str(os.getpid() )

# lancement process bash
p=subprocess.Popen( "./pypipe.sh", stdin=subprocess.PIPE,  shell=True)
pOut=p.communicate('TOTO=1\n'.encode())[0]
pOUT,  pERR = p.communicate('GET:TOTO\n'.encode())
print('(Python) TOTO=' + pOUT)
# fin du process bash
p.communicate('END\n'.encode())

print("FIN du test")

le script bash :

#!/bin/bash
set -x

py_fin=1
while [ ${py_fin} -eq 1 ]
do    
    read py_cmd

    #py_cmd="${py_cmd:-END}"
    [ "${py_cmd}" = "END" ] 
    py_fin=$?
    if [ ${py_fin} -eq 1 ]  && [ "${py_cmd}" ]; then
        if [[ "${py_cmd}" =~ ^GET:.+$ ]]; then
            py_variable="${py_cmd#GET:}"
            eval echo "\${${py_variable}[@]}"
        else
            eval "${py_cmd}"
        fi
    fi
done
exit

Mon constat :
Si je précise stdin en paramètre de Popen, le script python bloque à la ligne pOut=p.communicate('TOTO=1\n'.encode())[0]
Pour ce qui est du script bash, il boucle comme si ce qu'il lisait dans son stdin est vide hmm

Si je ne précise pas stdin, c'est le script shell qui bloque car il attend quelque chose dans stdin hmm

qu'est-ce qui m'échape ?

Dernière modification par Totor (Le 26/08/2009, à 13:15)


-- Lucid Lynx --

Hors ligne

#5 Le 26/08/2009, à 22:45

Totor

Re : [Résolu] Python et les pipes

Bonsoir,

J'ai trouvé une solution alternative qui me convient.

J'utilise désormais la package pexpect et je conserve le script bash.
(avec la seule doc "complète" que j'ai trouvé : http://pexpect.sourceforge.net/pexpect.html)


Cependant la compilation (lors de l'installation) de ce package pour python 3.0 n'aboutie pas. N'ayant pas les compétences nécessaires pour modifier le code, je suis donc passé à Python 2.6.

Pour les scripts, voilà ce que ça donne :
Python :

#!/usr/bin/python
import pexpect

def _py_status():
    return "py_status="

def initBash():
    child=child = pexpect.spawn('./pypipe.sh')
    if child.getecho():
            child.setecho(False)
    return child
    
def sendCmd(childProcess,  cmdToExecute):
    childProcess.sendline(cmdToExecute)
    
    flux=''
    retour=''
    if cmdToExecute != 'END':
        while not retour.startswith(_py_status()):
            retour=childProcess.readline()
            if not retour.startswith(_py_status()):
                if flux != '': fluc+='\n'
                flux+=retour
                print retour
            else:
                return [retour.lstrip(_py_status()),  flux ]
    else: return [ '0',  '' ]

def getValue(childProcess,  variableName):
    childProcess.sendline('GET:'+variableName)
    return childProcess.readline()

child = initBash()
print ('Status:' + sendCmd(child,'TOTO=1') [0])
print ('Status:' + sendCmd(child,'((TOTO++))') [0])
print ('Status:' + sendCmd(child,'echo "(Bash) TOTO=${TOTO}"') [0])
print('(Python)TOTO='+getValue(child,'TOTO'))
print ('Status:' + sendCmd(child,'TABLEAU=("1 2 3" 2 3)')[0])
print ('Status:' + sendCmd(child,'echo "(Bash) NB_ELE=${#TABLEAU[@]}"') [0])
print('(Python)TABLEAU='+getValue(child,'TABLEAU'))
sendCmd(child,'END')
child.wait()

Pour le bash :

#!/bin/bash
#set -x
py_fichierLog="/tmp/$(basename "$0" .sh).$$.log"
py_fichierErr="/tmp/$(basename "$0" .sh).$$.err"
py_fichierTrc="/tmp/$(basename "$0" .sh).$$.trc"

exec 1> >(tee -a "${py_fichierTrc}" )
exec 2> >(tee -a "${py_fichierErr}" >&2)
exec 3>"${py_fichierLog}"

echo "$(date) : Start listening ..." >&3

py_fin=1

while [ ${py_fin} -eq 1 ]
do 
    read py_cmd 
    
    echo "Recieved command : ${py_cmd}" >&3
    
    #py_cmd="${py_cmd:-END}"
    [ "${py_cmd}" = "END" ] 
    py_fin=$?
    if [ ${py_fin} -eq 1 ]  && [ "${py_cmd}" ]; then
        if [[ "${py_cmd}" =~ ^GET:.+$ ]]; then
            py_variable="${py_cmd#GET:}"
            py_value="$(eval echo "\${${py_variable}[@]}")"
            echo "Output value of ${py_variable} ---> ${py_value}" >&3
            echo "${py_value}" 
        else
            echo "Evaluating '${py_cmd}'..." >&3
            eval "${py_cmd}"
            py_status="$?"
            echo "Return status : ${py_status}" >&3
            echo "py_status=${py_status}"
        fi
    fi    
done

echo "$(date) : Stop listening." >&3
exit

Note : si quelqu'un trouve une solution avec les pipes (même si je compte garder cette solution) ou comment compiler le packages pexpect en python 3.0, je suis preneur

Dernière modification par Totor (Le 28/08/2009, à 02:29)


-- Lucid Lynx --

Hors ligne

Haut de page ↑