web-dev-qa-db-fra.com

Comment répliquer le comportement du tee dans Python lors de l'utilisation d'un sous-processus?

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

FYI: Je pose des questions sur tee (comme l'utilitaire de ligne de commande Unix) et non sur la fonction avec le même nom de Python module intertools.

Détails

  • Solution Python (n'appelant pas tee, elle n'est pas disponible sous Windows)
  • Je n'ai pas besoin de fournir d'entrée à stdin pour le processus appelé
  • Je n'ai aucun contrôle sur le programme appelé. Tout ce que je sais, c'est qu'il va sortir quelque chose vers stdout et stderr et retourner avec un code de sortie.
  • Pour fonctionner lors de l'appel de programmes externes (sous-processus)
  • Pour travailler à la fois sur stderr et stdout
  • Pouvoir faire la différence entre stdout et stderr parce que je ne veux afficher qu'un seul de la à la console ou je pourrais essayer de sortir stderr en utilisant une couleur différente - cela signifie que stderr = subprocess.STDOUT ne fonctionnera pas.
  • Sortie en direct (progressive) - le processus peut durer longtemps et je ne peux pas 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)
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--

La sortie escomptée devait être la commande des lignes. Remarque, la modification du Popen pour utiliser un seul PIPE n'est pas autorisée car dans la vraie vie, je voudrai faire différentes choses avec stderr et stdout.

Même dans le deuxième cas, je n'ai pas pu obtenir de sortie en temps réel, en fait, tous les résultats ont été reçus à la fin du processus. Par défaut, Popen ne doit utiliser aucun tampon (bufsize = 0).

65
sorin

Je vois que c'est un article plutôt ancien mais juste au cas où quelqu'un chercherait toujours un moyen de le faire:

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)
16
Ben

Enfin, j'ai dû implémenter la commande tee() dans Python moi-même).

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

Actuellement, cela 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 actuelle est qu'il n'est pas en mesure de faire la différence entre stderr et stdout, ce qui signifie qu'il fusionnera les deux.

13
sorin

Il s'agit d'un port simple de tee vers 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

Je suis sous Linux en ce moment mais cela devrait fonctionner sur la plupart des plateformes.


Maintenant, pour la partie subprocess, je ne sais pas comment vous voulez "câbler" les stdin, stdout et stderr du sous-processus à votre stdin, stdout, stderr et les récepteurs de fichiers, mais je sais que vous pouvez le faire:

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

Vous pouvez désormais accéder à callee.stdin , callee.stdout et callee.stderr comme des fichiers normaux, ce qui permet la "solution" ci-dessus pour fonctionner. Si vous voulez obtenir le callee.returncode , vous devrez faire un appel supplémentaire à callee.poll() .

Soyez prudent lorsque vous écrivez dans callee.stdin: Si le processus se termine lorsque vous faites cela, une erreur peut se produire (sous Linux, j'obtiens IOError: [Errno 32] Broken pipe).

7
badp

Il y a des problèmes/bogues subtils dans python lié au sous-processus.PIPE: http://bugs.python.org/issue1652

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

3
user553965

Si vous ne voulez pas interagir avec le processus, vous pouvez très bien utiliser le module de sous-processus.

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 tout d'abord écrire stdout/stderr dans un fichier. Vous pouvez également envoyer des arguments à votre processus avec Communiquer, même si je n'ai pas réussi à comprendre comment interagir en permanence avec le sous-processus.

3
Wayne Werner

Voilà comment cela peut être fait

import sys
from subprocess import Popen, PIPE

with open('log.log', 'w') as log:
    proc = Popen(["ping", "google.com"], stdout=PIPE, encoding='utf-8')
    while proc.poll() is None:
        text = proc.stdout.readline() 
        log.write(text)
        sys.stdout.write(text)
1
Danylo Zhydyk

J'ai écrit une chose qui encapsule les commandes Shell en Python.

Avantages clés:

  1. Cet utilitaire capture toujours stdout/stderr
  2. Cet utilitaire fournit une option pour faire écho de stdout/stderr à stdout/stderr pour le processus
  3. Lors de l'écho de stdout/stderr la sortie/err il n'y a pas de retard

Inconvénient clé:

  • Fonctionne uniquement sur bash/unix

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

0
Andrew Hoos

Essaye ça :

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)

Ma solution n'est pas élégante, mais elle fonctionne.

Vous pouvez utiliser PowerShell pour accéder à "tee" sous WinOS.

import subprocess
import sys

cmd = ['powershell', 'ping', 'google.com', '|', 'tee', '-a', 'log.txt']

if 'darwin' in sys.platform:
    cmd.remove('powershell')

p = subprocess.Popen(cmd)
p.wait()
0
Danylo Zhydyk