IFT585 - Q&R - Problème à utiliser subprocess.Popen avec succès
par Benoit, 2014-05-15

Question

Ca fait un bon moment que je suis coincé sur l’exécution de tee sur le serveur. Je sais qu’il y a les bonnes données dans le pipe stdin. J’ai une boucle qui l’affiche mais lorsque que je fais:

p = subprocess.Popen(bash,
    shell=True,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT)
out, err = p.communicate()#wait for tee to be done

Il me crée le fichier, mais il est toujours vide, comme si son stdin était vide. Je ne comprends absolument pas ce comportement, tout le reste semble fonctioner dans mes affaires.

Réponse

Ici, l’auteur de la question présente une approche d’utilisation d’une instance de subprocess.Popen différente de celle proposée dans l’énoncé du TP1. Je présume que la variable bash à la première ligne du bout de code inclus est une variable dûment définie à quelque chose comme la chaîne "/bin/bash"; le reste de la séquence d’appel du constructeur correspond à ce qui est présenté dans l’énoncé.

La différence réside dans l’usage de la méthode p.communicate(); dans l’énoncé, on utilisait plutôt directement les pipes p.stdin et p.stdout, ainsi que la méthode p.wait(). La méthode p.communicate() offre une sorte de raccourci pour échanger des données avec un coprocessus (i.e. un processus dont on fournit l’entrée et recueille la sortie). Quand on invoque

out, err = p.communicate(input)

Le programme reçoit sur son entrée standard les octets de la chaîne input – et c’est tout. L’appel attend ensuite que le programme démarré par le constructeur se termine, recueillant dans des chaînes en mémoire les octets écrits par le programme sur sa sortie standard et sur son erreur standard, et retourne le tout respectivement dans out et err. Ainsi, la méthode p.communicate() est idéale lorsqu’on échange des données avec un coprocessus non interactif: on donne des données en entrée une seule fois, on recueille les données en sortie une seule fois, puis le coprocessus est terminé et c’est tout!

Ce n’est pas du tout le genre de communication qu’on désire effectuer dans le TP1 entre le serveur et le programme interactif qu’il démarre. Nous désirons être en mesure d’effectuer un nombre arbitraire d’échanges d’informations. De plus, on ne veut pas recueillir les sorties du programme une seule fois, lorsqu’il se termine: on veut les recueillir au fur et à mesure que le programme les génère. Donc, je ne crois pas que p.communicate() soit l’approche à préconiser.

Pour revenir au comportement observé par l’auteur de la question, il est parfaitement en accord avec ma description du comportement de p.communicate(). Dans l’invocation ci-haut, en ne passant pas de paramètre, le pipe en entrée du shell coprocessus est immédiatement fermé; le shell ne reçoit donc aucune entrée. Lorsque le shell détecte la fin du fichier en entrée, il termine son exécution. C’est pourquoi, tant dans out que dans err, l’auteur de la question reçoit des chaînes vides.

Finalement, je ne suis pas certain, mais en relisant le texte original de la question, j’ai l’impression que l’auteur semble vouloir implanter son programme ltee et exécutant le programme tee… Ce serait une erreur. La similarité entre tee et ltee est conceptuelle, pas mécanique. Le programme tee sert à sauvegarder la sortie standard d’un programme dans un fichier sur la machine où le programme est exécuté, en plus d’en faire écho sur la sortie standard. Considérons que j’exécute la commande date de la manière suivante:

$ date | tee allo
Thu May 15 21:32:42 EDT 2014

Cette sortie de la commande date aurait aussi été sauvegardée dans le fichier allo dans le répertoire courant. Maintenant, dans un contexte où j’exécute cette même commande date sur un serveur distant via resh/reshd, je peux vouloir sauvegarder la sortie de la commande sur ma propre machine, plutôt que sur le serveur. J’utiliserais alors la commande ltee. Simulons – à vous de deviner qu’est-ce qui est une commande et qu’est-ce qui est une sortie, ainsi que qu’est-ce qui est exécuté localement vs. qu’est-ce qui est exécuté sur le serveur distant:

$ ls
resh
$ ./resh serveur.lointain.com 9887
ls
ltee  lcat  reshd  resh
date
Thu May 15 21:35:21 EDT 2014
id | tee allo
Thu May 15 21:35:33 EDT 2014
ls
allo  ltee  lcat  reshd  resh
cat allo
Thu May 15 21:35:33 EDT 2014
date | ./ltee chezmoi
Thu May 15 21:42:10 EDT 2014
ls
allo  ltee  lcat  reshd  resh
exit
$ ls
chezmoi  resh
$ cat chezmoi
Thu May 15 21:42:10 EDT 2014

Le lien entre la commande standard cat et la commande lcat que vous devrez programmer est identique: ces deux commandes sont conceptuellement analogues, mais leurs implantations respectives n’ont rien à voir. La commande lcat ne sera pas basée sur la commande cat; cette commande n’a pas à être exécutée par lcat.