Comment répliquer le comportement de tee en Python lors de l'utilisation de subprocess?

je suis à la recherche d'une solution Python qui me permettra de sauvegarder la sortie d'une commande dans un fichier sans la cacher de la console.

FYI: je demande à propos de (comme utilitaire de ligne de commande Unix) et non la fonction du même nom du module Python intertools.

Détails

  • Python solution (ne pas appeler tee, il n'est pas disponible sous Windows)
  • je n'ai pas besoin de fournir d'informations sur stdin pour les appelés du processus
  • Je n'ai aucun contrôle sur le programme appelé. Tout ce que je sais c'est qu'il va sortir quelque chose à stdout et stderr et revenir avec un code de sortie.
  • lors de l'appel de programmes externes (sous-processus)
  • De stderr et stdout
  • être capable de faire la différence entre stdout et stderr parce que je veux peut - être afficher un seul de la console ou je pourrais essayer de sortir stderr en utilisant une couleur différente- ceci signifie que stderr = subprocess.STDOUT ne fonctionne pas.
  • sortie Live (progressive) - le processus peut fonctionner pendant une longue période, Et je ne suis pas en mesure d'attendre qu'il se termine.
  • code compatible Python 3 (important)

Références

voici quelques solutions incomplètes que j'ai trouvées jusqu'à présent:

Diagramme http://blog.i18n.ro/wp-content/uploads/2010/06/Drawing_tee_py.png

code Actuel (deuxième essai)

#!/usr/bin/python
from __future__ import print_function

import sys, os, time, subprocess, io, threading
cmd = "python -E test_output.py"

from threading import Thread
class StreamThread ( Thread ):
    def __init__(self, buffer):
        Thread.__init__(self)
        self.buffer = buffer
    def run ( self ):
        while 1:
            line = self.buffer.readline()
            print(line,end="")
            sys.stdout.flush()
            if line == '':
                break

proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdoutThread = StreamThread(io.TextIOWrapper(proc.stdout))
stderrThread = StreamThread(io.TextIOWrapper(proc.stderr))
stdoutThread.start()
stderrThread.start()
proc.communicate()
stdoutThread.join()
stderrThread.join()

print("--done--")

#### test_output.py ####

#!/usr/bin/python
from __future__ import print_function
import sys, os, time

for i in range(0, 10):
    if i%2:
        print("stderr %s" % i, file=sys.stderr)
    else:
        print("stdout %s" % i, file=sys.stdout)
    time.sleep(0.1)
La production réelle
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--

L'extrant prévu était d'avoir les lignes ordonnées. Remarque, modifier le Popen pour utiliser un seul tuyau n'est pas autorisé parce que dans la vraie vie je vais envie de faire des choses différentes avec stderr et stdout.

même dans le second cas, je n'ai pas été capable d'obtenir des résultats en temps réel comme dehors, en fait tous les résultats ont été reçus à la fin du processus. Par défaut, Popen ne doit pas utiliser de tampon (bufsize=0).

42
demandé sur sorin 2010-06-08 15:36:13

7 réponses

je vois que c'est un vieux post, mais juste au cas où quelqu'un est toujours à la recherche d'un moyen de faire ceci:

proc = subprocess.Popen(["ping", "localhost"], 
                        stdout=subprocess.PIPE, 
                        stderr=subprocess.PIPE)

with open("logfile.txt", "w") as log_file:
  while proc.poll() is None:
     line = proc.stderr.readline()
     if line:
        print "err: " + line.strip()
        log_file.write(line)
     line = proc.stdout.readline()
     if line:
        print "out: " + line.strip()
        log_file.write(line)
10
répondu user1557760 2012-11-03 23:58:03

finalement j'ai dû mettre en oeuvre tee() commande en Python moi-même.

Vous pouvez l'obtenir à partir d'ici http://github.com/pycontribs/tendo/blob/master/tendo/tee.py

Actuellement, il vous permet de faire des choses comme:

 tee("python --v") # works just like os.system()

 tee("python --v", "log.txt") # file names

 tee("python --v", file_handle)

 import logging
 tee("python --v", logging.info) # receive a method

La seule limitation est qu'il n'est pas capable de différencier entre stderr et stdout, ce qui signifie qu'il va fusionner les deux.

6
répondu sorin 2016-02-12 21:01:15

ceci est un port simple de tee à Python.

import sys
sinks = sys.argv[1:]
sinks = [open(sink, "w") for sink in sinks]
sinks.append(sys.stderr)
while True:
  input = sys.stdin.read(1024)
  if input:
    for sink in sinks:
      sink.write(input)
  else:
    break

J'utilise Linux en ce moment, mais cela devrait fonctionner sur la plupart des plateformes.


Maintenant, pour le subprocess partie, je ne sais pas comment vous voulez "fil de fer" le sous-processus stdin,stdout et stderr pour votre stdin,stdout,stderr et le fichier de lavabos, mais je sais que vous pouvez faire ceci:

import subprocess
callee = subprocess.Popen( ["python", "-i"],
                           stdin = subprocess.PIPE,
                           stdout = subprocess.PIPE,
                           stderr = subprocess.PIPE
                         )

Maintenant vous pouvez accéder callee.stdin, callee.stdout et callee.stderr comme les fichiers normaux, permettant à la "solution" ci-dessus de fonctionner. Si vous voulez obtenir le callee.returncode, vous aurez besoin de faire un appel supplémentaire à callee.poll().

soyez prudent avec l'écriture de callee.stdin: si le processus s'est terminé quand vous faites cela, une erreur peut être monté (sur Linux, j'obtiens IOError: [Errno 32] Broken pipe).

4
répondu badp 2010-06-08 13:42:06

si vous ne voulez pas interagir avec le processus, vous pouvez utiliser le module subprocess très bien.

Exemple:

tester.py

import os
import sys

for file in os.listdir('.'):
    print file

sys.stderr.write("Oh noes, a shrubbery!")
sys.stderr.flush()
sys.stderr.close()

testing.py

import subprocess

p = subprocess.Popen(['python', 'tester.py'], stdout=subprocess.PIPE,
                     stdin=subprocess.PIPE, stderr=subprocess.PIPE)

stdout, stderr = p.communicate()
print stdout, stderr

Dans votre situation, vous pouvez simplement écrire stdout/stderr dans un fichier en premier. Vous pouvez envoyer des arguments à votre processus avec communicate aussi bien, bien que je n'ai pas été en mesure de comprendre comment interagir continuellement avec le sous-processus.

2
répondu Wayne Werner 2010-06-08 13:36:11

il y a des problèmes/bogues subtils en python liés au sous-processus.TUYAU: http://bugs.python.org/issue1652

apparemment ceci a été corrigé dans python3+, mais pas dans python 2.7 et plus ancien. Pour cela, vous devez utiliser: code.google.com/p/python-subprocess32/

2
répondu user553965 2010-12-28 01:30:32

essaye ceci :

import sys

class tee-function :

    def __init__(self, _var1, _var2) :

        self.var1 = _var1
        self.var2 = _var2

    def __del__(self) :

        if self.var1 != sys.stdout and self.var1 != sys.stderr :
            self.var1.close()
        if self.var2 != sys.stdout and self.var2 != sys.stderr :
            self.var2.close()

    def write(self, text) :

        self.var1.write(text)
        self.var2.write(text)

    def flush(self) :

        self.var1.flush()
        self.var2.flush()

stderrsav = sys.stderr

out = open(log, "w")

sys.stderr = tee-function(stderrsav, out)
0
répondu Catalin Festila 2010-07-01 09:36:27

j'ai écrit une chose qui enveloppe les commandes shell en Python.

avantages:

  1. C'util capture stdout/stderr toujours
  2. C'util fournit une option pour echo stdout/stderr vers stdout/stderr pour le processus
  3. lorsque l'écho stdout/stderr Le out/err il n'y a pas de délai

inconvénient:

  • fonctionne seulement sur bash / unix

source: https://gist.github.com/AndrewHoos/9f03c74988469b517a7a

0
répondu Andrew Hoos 2015-09-23 05:40:45