Wie leite ich stdout mit Skripten auf Datei und Konsole um?

75

Ich möchte ein Python-Skript ausführen und die Ausgabe in einer Textdatei erfassen sowie auf der Konsole anzeigen.

Ich möchte es als eine Eigenschaft des Python-Skripts selbst angeben. Verwenden Sie den Befehl NICHT echo "hello world" | tee test.txtjedes Mal an der Eingabeaufforderung.

Innerhalb des Skripts habe ich versucht:

sys.stdout = open('log.txt','w')

Dies zeigt jedoch nicht die Standardausgabe auf dem Bildschirm.

Ich habe von einem Protokollierungsmodul gehört, aber ich konnte kein Glück haben, dieses Modul für die Arbeit zu verwenden.

user2033758
quelle

Antworten:

126

Sie können die Shell-Umleitung verwenden, während Sie die Python-Datei ausführen:

python foo_bar.py > file

Dadurch werden alle auf stdout gedruckten Ergebnisse von der Python-Quelle in die Datei in die Protokolldatei geschrieben.

Oder wenn Sie sich innerhalb des Skripts anmelden möchten:

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("logfile.log", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

    def flush(self):
        #this flush method is needed for python 3 compatibility.
        #this handles the flush command by doing nothing.
        #you might want to specify some extra behavior here.
        pass    

sys.stdout = Logger()

Jetzt können Sie verwenden:

print "Hello"

Dadurch wird "Hallo" sowohl in stdout als auch in die Protokolldatei geschrieben

Amith Koujalgi
quelle
1
Hallo Amith, ich möchte dies nicht verwenden, da hierfür eine manuelle Interaktion erforderlich ist (> Datei). Gibt es etwas, mit dem ich auch im Skript arbeiten kann, oder wenn die Ausführung abgeschlossen ist, was auch immer auf der Konsole angekommen ist, nimmt und pusht es in eine Datei?
user2033758
@ user2033758: Sobald das Programm beendet ist und sich die Ausführung auf der Konsole befindet, hat das Programm keine Kontrolle mehr darüber.
Anthon
1
@ user2033758: Diese Antwort enthält zwei Vorschläge, und der zweite erfordert keine manuelle Interaktion. Sie können entweder die Befehlszeile oder die Klasse in Ihrem Code verwenden. Ich habe dies getestet und die Klasse sendet die Ausgabe sowohl an die Konsole als auch an die Datei ohne spezielle Parameter in der Befehlszeile.
JDM
Wie bekomme ich das normale sys.stdoutVerhalten zurück? Angenommen, ich möchte dies, aber nur in der Startphase meines Programms, und nach einer Weile möchte ich das Programm einfach laufen lassen, ohne alles zu speichern
Toke Faurby
@TokeFaurbyYou könnte das vorherige sys.stdoutin Logger speichern und eine Funktion erstellen, die es in den ursprünglichen Zustand zurücksetzt .
Gábor Fekete
20

Ich habe die Möglichkeit, die Ausgabe gleichzeitig an die Konsole sowie an eine Textdatei umzuleiten:

te = open('log.txt','w')  # File where you need to keep the logs

class Unbuffered:

   def __init__(self, stream):

       self.stream = stream

   def write(self, data):

       self.stream.write(data)
       self.stream.flush()
       te.write(data)    # Write the data of stdout here to a text file as well



sys.stdout=Unbuffered(sys.stdout)
user2033758
quelle
15

Verwenden Sie das Protokollierungsmodul, um Ihre App zu debuggen und zu verfolgen

Hier ist, wie ich es geschafft habe, mich in einer Datei und in console / stdout anzumelden

import logging
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    filename='logs_file',
                    filemode='w')
# Until here logs only to file: 'logs_file'

# define a new Handler to log to console as well
console = logging.StreamHandler()
# optional, set the logging level
console.setLevel(logging.INFO)
# set a format which is the same for console use
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)

# Now, we can log to both ti file and console
logging.info('Jackdaws love my big sphinx of quartz.')
logging.info('Hello world')

Lesen Sie es aus der Quelle: https://docs.python.org/2/howto/logging-cookbook.html

Radu Gabriel
quelle
10

Ich habe eine einfachere Lösung gefunden. Definieren Sie einfach eine Funktion, die in eine Datei oder auf einen Bildschirm oder in beide gedruckt wird. Im folgenden Beispiel erlaube ich dem Benutzer, den Namen der Ausgabedatei als Argument einzugeben, aber das ist nicht obligatorisch:

OutputFile= args.Output_File
OF = open(OutputFile, 'w')

def printing(text):
    print text
    if args.Output_File:
        OF.write(text + "\n")

Danach ist alles, was zum Drucken einer Zeile sowohl in die Datei als auch in den Bildschirm benötigt wird, Folgendes: Drucken (Line_to_be_printed)

Hector Garcia
quelle
1
Einfache und brillante Idee, um es mit einer benutzerdefinierten Funktion zu handhaben.
Akif
1
Das funktioniert. Beachten Sie jedoch, dass die Eingabefunktion 'Text' in 'Drucken' expliziter Text sein muss. Es werden keine zusätzlichen Argumente unterstützt, die print unterstützt.
Rrlamichhane
Mein Problem bei der Unterstützung von nicht explizitem Text (wie datetime.datetime) lag in der Schreibfunktion. Ich habe gerade Text mit umgeben str(text), um ihn in einen String umzuwandeln.
Tzg
10

Basierend auf der Antwort von Amith Koujalgi ist hier ein einfaches Modul, das Sie zum Protokollieren verwenden können:

transkript.py:

"""
Transcript - direct print output to a file, in addition to terminal.

Usage:
    import transcript
    transcript.start('logfile.log')
    print("inside file")
    transcript.stop()
    print("outside file")
"""

import sys

class Transcript(object):

    def __init__(self, filename):
        self.terminal = sys.stdout
        self.logfile = open(filename, "a")

    def write(self, message):
        self.terminal.write(message)
        self.logfile.write(message)

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        # this handles the flush command by doing nothing.
        # you might want to specify some extra behavior here.
        pass

def start(filename):
    """Start transcript, appending print output to given filename"""
    sys.stdout = Transcript(filename)

def stop():
    """Stop transcript and return print functionality to normal"""
    sys.stdout.logfile.close()
    sys.stdout = sys.stdout.terminal
Brian Burns
quelle
6
from IPython.utils.io import Tee
from contextlib import closing

print('This is not in the output file.')        

with closing(Tee("outputfile.log", "w", channel="stdout")) as outputstream:
    print('This is written to the output file and the console.')
    # raise Exception('The file "outputfile.log" is closed anyway.')
print('This is not written to the output file.')   

# Output on console:
# This is not in the output file.
# This is written to the output file and the console.
# This is not written to the output file.

# Content of file outputfile.txt:
# This is written to the output file and the console.

Die TeeKlasse in IPython.utils.iomacht, was Sie wollen, aber es fehlen die __enter__und __exit__Methoden, die erforderlich sind, um sie in der withAnweisung aufzurufen. Diese werden von hinzugefügt contextlib.closing.

oha
quelle
5

Ich habe hier einige Lösungen ausprobiert und nicht die gefunden, die gleichzeitig in eine Datei und in eine Konsole schreibt. Also hier ist was ich getan habe (basierend auf dieser Antwort)

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout

    def write(self, message):
        with open ("logfile.log", "a", encoding = 'utf-8') as self.log:            
            self.log.write(message)
        self.terminal.write(message)

    def flush(self):
        #this flush method is needed for python 3 compatibility.
        #this handles the flush command by doing nothing.
        #you might want to specify some extra behavior here.
        pass
sys.stdout = Logger()   

Diese Lösung verbraucht mehr Rechenleistung, speichert jedoch zuverlässig alle Daten von stdout in der Logger-Datei und benötigt weniger Speicher. Für meine Bedürfnisse habe ich auch einen Zeitstempel in self.log.write (Nachricht) hinzugefügt. Funktioniert super.

Sanon Selkov
quelle
3

Hier ist ein einfacher Kontextmanager, der auf der Konsole druckt und dieselbe Ausgabe in eine Datei schreibt. Es werden auch alle Ausnahmen in die Datei geschrieben.

import traceback
import sys

# Context manager that copies stdout and any exceptions to a log file
class Tee(object):
    def __init__(self, filename):
        self.file = open(filename, 'w')
        self.stdout = sys.stdout

    def __enter__(self):
        sys.stdout = self

    def __exit__(self, exc_type, exc_value, tb):
        sys.stdout = self.stdout
        if exc_type is not None:
            self.file.write(traceback.format_exc())
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def flush(self):
        self.file.flush()
        self.stdout.flush()

So verwenden Sie den Kontextmanager:

print("Print")
with Tee('test.txt'):
    print("Print+Write")
    raise Exception("Test")
print("Print")
Supersolver
quelle
Gute Antwort! In Python3 habe ich einen Fehler erhalten, weil "isatty" fehlt. Um dies zu beheben, fügen Sie Folgendes hinzu: def isatty (self): return False
matt3o
1

Um die Ausgabe in eine Datei und ein Terminal umzuleiten, ohne die Verwendung Ihres Python-Skripts im Freien zu ändern, können Sie Folgendes verwenden pty.spawn(itself):

#!/usr/bin/env python
"""Redirect stdout to a file and a terminal inside a script."""
import os
import pty
import sys

def main():
    print('put your code here')

if __name__=="__main__":
    sentinel_option = '--dont-spawn'
    if sentinel_option not in sys.argv:
        # run itself copying output to the log file
        with open('script.log', 'wb') as log_file:
            def read(fd):
                data = os.read(fd, 1024)
                log_file.write(data)
                return data

            argv = [sys.executable] + sys.argv + [sentinel_option]
            rc = pty.spawn(argv, read)
    else:
        sys.argv.remove(sentinel_option)
        rc = main()
    sys.exit(rc)

Wenn das ptyModul nicht verfügbar ist (unter Windows), können Sie es durch eine portablere teed_call()Funktion ersetzen , die jedoch normale Pipes anstelle eines Pseudo-Terminals bereitstellt. Dies kann das Verhalten einiger Programme ändern.

Der Vorteil von pty.spawnund subprocess.Popen-basierten Lösungen gegenüber dem Ersetzen sys.stdoutdurch ein dateiähnliches Objekt besteht darin, dass sie die Ausgabe auf Dateideskriptorebene erfassen können, z. B. wenn das Skript andere Prozesse startet, die auch eine Ausgabe auf stdout / stderr erzeugen können. Siehe meine Antwort auf die verwandte Frage: Stdout in eine Datei in Python umleiten?

jfs
quelle
0

Basierend auf der von Brian Burns bearbeiteten Antwort habe ich eine einzelne Klasse erstellt, die einfacher aufzurufen ist:

class Logger(object):

    """
    Class to log output of the command line to a log file

    Usage:
    log = Logger('logfile.log')
    print("inside file")
    log.stop()
    print("outside file")
    log.start()
    print("inside again")
    log.stop()
    """

    def __init__(self, filename):
        self.filename = filename

    class Transcript:
        def __init__(self, filename):
            self.terminal = sys.stdout
            self.log = open(filename, "a")
        def __getattr__(self, attr):
            return getattr(self.terminal, attr)
        def write(self, message):
            self.terminal.write(message)
            self.log.write(message)
        def flush(self):
            pass

    def start(self):
        sys.stdout = self.Transcript(self.filename)

    def stop(self):
        sys.stdout.log.close()
        sys.stdout = sys.stdout.terminal
Crazjo
quelle
-2

Ich habe es versucht:

"""
Transcript - direct print output to a file, in addition to terminal.

Usage:
    import transcript
    transcript.start('logfile.log')
    print("inside file")
    transcript.stop()
    print("outside file")
"""

import sys

class Transcript(object):

    def __init__(self, filename):
        self.terminal = sys.stdout, sys.stderr
        self.logfile = open(filename, "a")

    def write(self, message):
        self.terminal.write(message)
        self.logfile.write(message)

    def flush(self):
        # this flush method is needed for python 3 compatibility.
        # this handles the flush command by doing nothing.
        # you might want to specify some extra behavior here.
        pass

def start(filename):
    """Start transcript, appending print output to given filename"""
    sys.stdout = Transcript(filename)

def stop():
    """Stop transcript and return print functionality to normal"""
    sys.stdout.logfile.close()
    sys.stdout = sys.stdout.terminal
    sys.stderr = sys.stderr.terminal
fxs
quelle
-5

Sie können die Ausgabe in eine Datei umleiten, indem Sie >> python mit der in den Dokumenten angegebenen "Chevron" -Syntax von print rint verwenden

mal sehen,

fp=open('test.log','a')   # take file  object reference 
print >> fp , "hello world"            #use file object with in print statement.
print >> fp , "every thing will redirect to file "
fp.close()    #close the file 

Checken Sie die Datei test.log aus, Sie haben die Daten und um auf der Konsole zu drucken, verwenden Sie einfach eine einfache Druckanweisung .

Neotam
quelle
1
>>ist nicht der Umleitungsoperator, sondern der Schichtoperator. Ändern Sie dies bitte.
nbro
Dies gilt in Python2.7. >> kann mit der print-Anweisung verwendet werden, um die Zeichenfolge zum angegebenen Dateiobjekt umzuleiten. Bitte überprüfen Sie, indem Sie meine Lösung selbst
ausprobieren