web-dev-qa-db-fra.com

Python: subprocess.call, stdout to file, stderr to file, affiche stderr à l'écran en temps réel

J'ai un outil de ligne de commande (en fait, plusieurs) pour lequel j'écris un wrapper en Python. 

L'outil est généralement utilisé comme ceci:

 $ path_to_tool -option1 -option2 > file_out

L'utilisateur obtient le résultat écrit dans file_out, et peut également voir divers messages d'état de l'outil en cours d'exécution. 

Je veux reproduire ce problème, tout en enregistrant stderr (les messages d'état) dans un fichier. 

Ce que j'ai est ceci:

from subprocess import call
call(['path_to_tool','-option1','option2'], stdout = file_out, stderr = log_file)

Cela fonctionne très bien SAUF que stderr n’est pas écrit à l’écran. Je peux ajouter du code pour imprimer le contenu du fichier log_file sur l'écran, mais l'utilisateur le verra une fois que tout sera terminé plutôt que pendant qu'il se passe. 

Pour récapituler, le comportement souhaité est:

  1. utilisez call () ou subprocess () 
  2. diriger la sortie standard vers un fichier 
  3. dirigez stderr vers un fichier, tout en écrivant également stderr à l'écran en temps réel, comme si l'outil avait été appelé directement à partir de la ligne de commande.

J'ai le sentiment qu'il me manque quelque chose de très simple, ou que c'est beaucoup plus compliqué que je ne le pensais ... merci pour toute aide!

EDIT: cela ne doit fonctionner que sous Linux.

25
Ben S.

J'ai dû apporter quelques modifications à la réponse de @ abarnert pour Python 3. Cela semble fonctionner:

def tee_pipe(pipe, f1, f2):
    for line in pipe:
        f1.write(line)
        f2.write(line)

proc = subprocess.Popen(["/bin/echo", "hello"],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)

# Open the output files for stdout/err in unbuffered mode.
out_file = open("stderr.log", "wb", 0)
err_file = open("stdout.log", "wb", 0)

stdout = sys.stdout
stderr = sys.stderr

# On Python3 these are wrapped with BufferedTextIO objects that we don't
# want.
if sys.version_info[0] >= 3:
    stdout = stdout.buffer
    stderr = stderr.buffer

# Start threads to duplicate the pipes.
out_thread = threading.Thread(target=tee_pipe,
                              args=(proc.stdout, out_file, stdout))
err_thread = threading.Thread(target=tee_pipe,
                              args=(proc.stderr, err_file, stderr))

out_thread.start()
err_thread.start()

# Wait for the command to finish.
proc.wait()

# Join the pipe threads.
out_thread.join()
err_thread.join()
1
Timmmm

Je pense que ce que vous recherchez ressemble à quelque chose comme:

import sys, subprocess
p = subprocess.Popen(cmdline,
                     stdout=sys.stdout,
                     stderr=sys.stderr)

Pour que la sortie/le journal soit écrit dans un fichier, je modifierais ma cmdline afin d'inclure les redirections habituelles, comme cela aurait été le cas sur un simple bash/shell linux. Par exemple, je voudrais ajouter tee à la ligne de commande: cmdline += ' | tee -a logfile.txt'

J'espère que cela pourra aider.

0
Brandt