web-dev-qa-db-fra.com

Pouvez-vous faire un python sous-processus de sortie stdout et stderr comme d'habitude, mais aussi capturer la sortie sous forme de chaîne?

Duplicata possible:
Envelopper le sous-processus stdout/stderr

Dans cette question , hanan-n a demandé s'il était possible d'avoir un sous-processus python qui sort en sortie standard tout en conservant la sortie dans une chaîne pour un traitement ultérieur. La solution dans ce cas était de boucler sur chaque ligne de sortie et de les imprimer manuellement:

output = []
p = subprocess.Popen(["the", "command"], stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, ''):
    print(line)
    output.append(line)

Cependant, cette solution ne se généralise pas dans le cas où vous voulez le faire pour stdout et stderr, tout en satisfaisant les points suivants:

  • la sortie de stdout/stderr doit aller respectivement au stdout/stderr du processus parent
  • la sortie doit se faire en temps réel autant que possible (mais j'ai seulement besoin d'accéder aux cordes à la fin)
  • l'ordre entre les lignes stdout et stderr ne doit pas être modifié (je ne sais pas trop comment cela fonctionnerait même si le sous-processus vide ses caches stdout et stderr à différents intervalles; supposons pour l'instant que nous obtenons tout dans Nice morceaux qui contiennent plein lignes?)

J'ai parcouru la documentation du sous-processus , mais je n'ai rien trouvé qui puisse y parvenir. Le plus proche que j'ai pu trouver est d'ajouter stderr=subprocess.stdout et utiliser la même solution que ci-dessus, mais nous perdons alors la distinction entre sortie régulière et erreurs. Des idées? Je suppose que la solution - s'il y en a une - impliquera d'avoir des lectures asynchrones sur p.stdout et p.stderr.

Voici un exemple de ce que j'aimerais faire:

p = subprocess.Popen(["the", "command"])
p.wait()  # while p runs, the command's stdout and stderr should behave as usual
p_stdout = p.stdout.read()  # unfortunately, this will return '' unless you use subprocess.PIPE
p_stderr = p.stderr.read()  # ditto
[do something with p_stdout and p_stderr]
31
pflaquerre

Cet exemple semble fonctionner pour moi:

# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4

import subprocess
import sys
import select

p = subprocess.Popen(["find", "/proc"],
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)

stdout = []
stderr = []

while True:
    reads = [p.stdout.fileno(), p.stderr.fileno()]
    ret = select.select(reads, [], [])

    for fd in ret[0]:
        if fd == p.stdout.fileno():
            read = p.stdout.readline()
            sys.stdout.write('stdout: ' + read)
            stdout.append(read)
        if fd == p.stderr.fileno():
            read = p.stderr.readline()
            sys.stderr.write('stderr: ' + read)
            stderr.append(read)

    if p.poll() != None:
        break

print 'program ended'

print 'stdout:', "".join(stdout)
print 'stderr:', "".join(stderr)

En général, dans toute situation où vous voulez faire des trucs avec plusieurs descripteurs de fichiers en même temps et vous ne savez pas lequel aura des trucs à lire, vous devez utiliser select ou quelque chose d'équivalent (comme un réacteur Twisted).

35

Pour imprimer sur la console et capturer dans une chaîne stdout/stderr d'un sous-processus de manière portable:

from StringIO import StringIO

fout, ferr = StringIO(), StringIO()
exitcode = teed_call(["the", "command"], stdout=fout, stderr=ferr)
stdout = fout.getvalue()
stderr = ferr.getvalue()

teed_call() est défini dans le sous-processus Python récupère la sortie des enfants dans le fichier et le terminal?

Vous pouvez utiliser n'importe quel objet de type fichier (méthode .write()).

9
jfs

Créez deux lecteurs comme ci-dessus, un pour stdout un pour stderr et démarrez chacun dans un nouveau thread. Cela s'ajouterait à la liste dans à peu près le même ordre de sortie que par le processus. Maintenez deux listes distinctes si vous le souhaitez.

c'est à dire.,

p = subprocess.Popen(["the", "command"])
t1 = thread.start_new_thread(func,stdout)  # create a function with the readers
t2 = thread.start_new_thread(func,stderr)
p.wait() 
# your logic here
2
dfb