2 votes

Capturer la sortie d'un programme C sur stdout en utilisant un sous-processus Python ? Pexpect ?

Je veux capturer la sortie d'un programme C que je lance comme ceci :

p = subprocess.Popen(["make", "run_pci"],
                     stdout=subprocess.PIPE,
                     cwd="/home/ecorbett/hello_world_pthread")
for ln in p.stdout:

Le seul problème est que je n'obtiens pas de sortie tant que le programme C n'est pas terminé, alors qu'en fait j'ai besoin d'obtenir une sortie ligne par ligne pendant l'exécution du programme. Et pour compliquer encore les choses, je dois analyser chaque ligne (je n'ai besoin que de certaines données des lignes).

Par exemple, voici quelques exemples de résultats : (J'ai besoin de capturer "Thread on Tile #")

blahblah blah Thread blahblah blah tile 1: On 
blahblah blah Thread blahblah blah tile 2: OFF 
blahblah blah Thread blahblah blah tile 3 : Disable

J'ai remarqué que l'article que j'ai mis en lien ci-dessous semble avoir le même problème. J'essaie de comprendre comment l'adapter à ma situation ?

Obtenir la sortie en temps réel de ffmpeg pour l'utiliser dans la barre de progression (PyQt4, stdout)

Je suis novice en Python, alors un exemple de code serait très apprécié !

3voto

ecatmur Points 64173

La raison pour laquelle vous devez utiliser pexpect est que le stdio du programme utilisera la mise en mémoire tampon en bloc s'il n'est pas connecté à un tty. pexpect utilise un pseudo-tty (pty), donc stdio utilisera la mise en tampon des lignes et vous pourrez accéder aux lignes au fur et à mesure de leur sortie.

Changez votre code pour :

p = pexpect.spawn('make', ['run_pci'], cwd="/home/ecorbett/hello_world_pthread")
for ln in p:
    ...

Vous pouvez utiliser pexpect.spawn.expect pour n'obtenir que les résultats qui vous intéressent :

while p.expect('Thread on Tile (\d+):', pexpect.EOF) == 0:
    print("Tile {0}".format(int(p.group[1])))

2voto

lserni Points 19089

Vous ne pouvez pas utiliser p.stdout comme ça ; si vous demandez "tout le stdout", il ne sera disponible qu'à la fin du processus (ou au moment du remplissage du tampon du tuyau, ce qui peut prendre beaucoup de temps).

Vous devez lire le stdout du processus ligne par ligne.

while True:
    ln = p.stdout.readline()
    if '' == ln:
        break
    m = re.search("Thread (?P<id>\d+)", ln);
    if m:
        # use m.group() to extract information
        # e.g. m.group('id') will hold the 12345 from "Thread 12345"

Il serait également préférable que stdout puisse être mis en tampon de ligne (habituellement il est entièrement mis en tampon lorsque cela est possible), mais je pense que cela ne peut être fait qu'à partir du programme appelé.

Nous avons deux tampons à considérer ici. Le premier est le tampon de sortie du programme C. Il peut être inexistant (sortie non tamponnée), tamponné par ligne ou entièrement tamponné (1K, 4K ou 8K sont quelques tailles possibles).

Dans le programme, un "printf()" est appelé. La sortie est la suivante :

  • sortie, si non tamponné
  • dans le tampon ; et ensuite toutes les lignes terminées par une nouvelle ligne dans le tampon sont sorties, si la ligne est mise en tampon ;
  • dans la mémoire tampon ; puis les premiers 4K sont sortis, si la mémoire tampon est entièrement remplie de 4K et que la mémoire tampon est plus remplie que 4K.

Maintenant la sortie entre dans le pipe de Python. Là encore, elle peut être entièrement mise en tampon (stdout) ou en ligne (readline). Ainsi, la sortie est envoyée :

  • selon la logique du programme python, s'il y a une ligne complète terminée par une nouvelle ligne dans le pipeline et que nous utilisons readline
  • dans le tampon, s'il y a moins de 4K dans le pipeline et que nous utilisons "for ln in stdout".

Dans ce dernier cas, le tampon sera envoyé en morceaux de 4K à la logique Python.

Imaginons maintenant un ligne mise en mémoire tampon Programme C produisant une ligne de 1K caractères par seconde vers un programme Python (si le programme C est entièrement mis en mémoire tampon, il n'y a pas grand chose à faire !)

En lisant stdout en cycle, nous verrions (à l'intérieur de la boucle for) :

  • t = 0 ... rien
  • t = 1 ... rien (le tampon est rempli à 50%)
  • t = 2 ... rien (le tampon est rempli à 75%)
  • t = 3 ... QUATRE lignes de sortie
  • t = 4 ... rien ...

En lisant à travers readline on obtiendrait :

  • t = 0 ... une ligne
  • t = 1 ... une ligne
  • t = 2 ... une ligne
  • t = 3 ... une ligne

EXEMPLE

Ici, je lance "ping -c 3 -i 2 127.0.0.1" afin d'obtenir trois paquets vers localhost à deux secondes d'intervalle. Une exécution de ping prend environ six secondes. Je lis la sortie de ping, et j'imprime un horodatage. L'ensemble de la sortie de ping est suffisamment petit pour tenir dans le tampon complet de Python.

#!/usr/bin/python

import subprocess
from time import gmtime, strftime

p = subprocess.Popen(["ping", "-c", "3", "-i", "2", "127.0.0.1"],
                 stdout=subprocess.PIPE)

for ln in p.stdout:
    print strftime("%H:%M:%S", gmtime()) + " received " + ln

# Now I start the same process again, reading the input the other way.

p = subprocess.Popen(["ping", "-c", "3", "-i", "2", "127.0.0.1"],
                 stdout=subprocess.PIPE)

while True:
    ln = p.stdout.readline()
    if '' == ln:
            break
    print strftime("%H:%M:%S", gmtime()) + " received " + ln

La sortie que je reçois sur ma boîte Linux est, comme prévu :

(nothing for the first six seconds)
15:40:10 received PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.034 ms
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.031 ms
15:40:10 received
15:40:10 received --- 127.0.0.1 ping statistics ---
15:40:10 received 3 packets transmitted, 3 received, 0% packet loss, time 3998ms
15:40:10 received rtt min/avg/max/mdev = 0.031/0.034/0.037/0.002 ms

15:40:10 received PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
15:40:10 received 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.041 ms
15:40:12 received 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.039 ms
15:40:14 received 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.035 ms
15:40:14 received
15:40:14 received --- 127.0.0.1 ping statistics ---
15:40:14 received 3 packets transmitted, 3 received, 0% packet loss, time 3999ms
15:40:14 received rtt min/avg/max/mdev = 0.035/0.038/0.041/0.005 ms

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X