web-dev-qa-db-fra.com

Python Popen: écrire simultanément sur stdout ET le fichier journal

J'utilise Popen pour appeler un script Shell qui écrit continuellement ses stdout et stderr dans un fichier journal. Existe-t-il un moyen de sortir simultanément le fichier journal en continu (à l'écran), ou de faire en sorte que le script Shell écrive à la fois dans le fichier journal et stdout en même temps?

Je veux essentiellement faire quelque chose comme ça en Python:

cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script

Encore une fois, ces tuyaux stderr/stdout ensemble pour tee, ce qui l'écrit à la fois sur stdout et mon fichier journal.

Je sais comment écrire stdout et stderr dans un fichier journal en Python. Là où je suis coincé, c'est comment les reproduire à l'écran:

subprocess.Popen("cat file", Shell=True, stdout=logfile, stderr=logfile)

Bien sûr, je pourrais simplement faire quelque chose comme ça, mais y a-t-il un moyen de le faire sans redirection des descripteurs de fichiers Shell et Shell?:

subprocess.Popen("cat file 2>&1 | tee -a logfile", Shell=True)
35
imagineerThat

Vous pouvez utiliser un canal pour lire les données de la sortie standard du programme et les écrire à tous les endroits que vous souhaitez:

import sys
import subprocess

logfile = open('logfile', 'w')
proc=subprocess.Popen(['cat', 'file'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
    sys.stdout.write(line)
    logfile.write(line)
proc.wait()

MISE À JOUR

Dans python 3, le paramètre universal_newlines Contrôle la façon dont les tuyaux sont utilisés. Si False, les lectures de tuyau renvoient les objets bytes et peuvent avoir besoin d'être décodé (par exemple, line.decode('utf-8')) pour obtenir une chaîne. Si True, python fait le décodage pour vous

Modifié dans la version 3.3: Lorsque universal_newlines a la valeur True, la classe utilise l'encodage locale.getpreferredencoding (False) au lieu de locale.getpreferredencoding (). Voir la classe io.TextIOWrapper pour plus d'informations sur cette modification.

35
tdelaney

Pour émuler: subprocess.call("command 2>&1 | tee -a logfile", Shell=True) sans appeler la commande tee:

#!/usr/bin/env python2
from subprocess import Popen, PIPE, STDOUT

p = Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1)
with p.stdout, open('logfile', 'ab') as file:
    for line in iter(p.stdout.readline, b''):
        print line,  #NOTE: the comma prevents duplicate newlines (softspace hack)
        file.write(line)
p.wait()

Pour résoudre les éventuels problèmes de mise en mémoire tampon (si la sortie est retardée), voir les liens dans Python: lire l'entrée de streaming depuis subprocess.communicate () .

Voici Python 3 version:

#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE, STDOUT

with Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1) as p, \
     open('logfile', 'ab') as file:
    for line in p.stdout: # b'\n'-separated lines
        sys.stdout.buffer.write(line) # pass bytes as is
        file.write(line)
13
jfs

Ecriture octet par octet sur le terminal pour les applications interactives

Cette méthode écrit immédiatement tous les octets qu'elle obtient sur stdout, ce qui simule de plus près le comportement de tee, en particulier pour les applications interactives.

main.py

#!/usr/bin/env python3
import os
import subprocess
import sys
with subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc, \
        open('logfile.txt', 'bw') as logfile:
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            logfile.write(byte)
            # logfile.flush()
        else:
            break
exit_status = proc.returncode

sleep.py

#!/usr/bin/env python3
import sys
import time
for i in range(10):
    print(i)
    sys.stdout.flush()
    time.sleep(1)

D'abord, nous pouvons faire un bilan de santé non interactif:

./main.py ./sleep.py

Et nous le voyons compter sur la sortie en temps réel.

Ensuite, pour un test interactif, vous pouvez exécuter:

./main.py bash

Ensuite, les caractères que vous tapez apparaissent immédiatement sur le terminal lorsque vous les tapez, ce qui est très important pour les applications interactives. Voici ce qui se passe lorsque vous exécutez:

bash | tee logfile.txt

De plus, si vous souhaitez que la sortie s'affiche immédiatement dans le fichier ouptut, vous pouvez également ajouter un:

logfile.flush()

mais tee ne fait pas cela, et je crains que cela ne tue les performances. Vous pouvez le tester facilement avec:

tail -f logfile.txt

Question connexe: sortie en direct de la commande de sous-processus

Testé sur Ubuntu 18.04, Python 3.6.7.