Stdout in eine Datei in Python umleiten?

314

Wie leite ich stdout in eine beliebige Datei in Python um?

Wenn ein lang laufendes Python-Skript (z. B. eine Webanwendung) innerhalb der SSH-Sitzung gestartet und zurückgesetzt wird und die SSH-Sitzung geschlossen wird, löst die Anwendung IOError aus und schlägt fehl, sobald versucht wird, in stdout zu schreiben. Ich musste einen Weg finden, die Anwendung und die Module in eine Datei anstatt in stdout auszugeben, um einen Fehler aufgrund von IOError zu verhindern. Derzeit verwende ich nohup, um die Ausgabe in eine Datei umzuleiten, und das erledigt den Job, aber ich habe mich aus Neugier gefragt, ob es eine Möglichkeit gibt, dies ohne Verwendung von nohup zu tun.

Ich habe es bereits versucht sys.stdout = open('somefile', 'w'), aber dies scheint nicht zu verhindern, dass einige externe Module weiterhin an das Terminal ausgegeben werden (oder die sys.stdout = ...Leitung wurde möglicherweise überhaupt nicht ausgelöst). Ich weiß, dass es mit einfacheren Skripten funktionieren sollte, mit denen ich getestet habe, aber ich hatte auch noch keine Zeit, um eine Webanwendung zu testen.

Eric Leschinski
quelle
8
Das ist nicht wirklich eine Python-Sache, es ist eine Shell-Funktion. Führen Sie einfach Ihr Skript wiescript.p > file
Falmarri
Ich löse derzeit das Problem mit nohup, aber ich dachte, es könnte etwas
1
@ Foxbunny: Nein? Warum einfach someprocess | python script.py? Warum einbeziehen nohup?
S.Lott
3
Schreiben Sie die printAnweisungen neu, um das loggingModul aus der stdlib anzuwenden. Dann können Sie die Ausgabe überall umleiten, steuern, wie viel Ausgabe Sie möchten usw. In den meisten Fällen sollte Produktionscode printaber nicht log.
Erikbwork
2
Eine bessere Lösung für dieses Problem ist möglicherweise der Befehl screen, mit dem Sie Ihre Bash-Sitzung speichern und von verschiedenen Läufen aus darauf zugreifen können.
Ryan Amos

Antworten:

403

Wenn Sie die Umleitung innerhalb des Python-Skripts durchführen möchten, reicht die Einstellung sys.stdoutauf ein Dateiobjekt aus:

import sys
sys.stdout = open('file', 'w')
print('test')

Eine weitaus häufigere Methode ist die Verwendung der Shell-Umleitung bei der Ausführung (unter Windows und Linux):

$ python foo.py > file
Moinudin
quelle
3
Wenn Sie unter Windows sind, achten Sie auf Windows-Fehler - Ausgabe kann nicht umgeleitet werden, wenn ich Python-Skript unter Windows nur mit dem Namen des Skripts ausführe
Piotr Dobrogost
7
Es funktioniert nicht mit from sys import stdout, vielleicht weil es eine lokale Kopie erstellt. Sie können es auch verwenden mit withz with open('file', 'w') as sys.stdout: functionThatPrints(). Sie können jetzt functionThatPrints()mit normalen printAnweisungen implementieren .
mgold
41
Es ist am besten, eine lokale Kopie aufzubewahren, stdout = sys.stdoutdamit Sie sie zurücksetzen können, wenn Sie fertig sind sys.stdout = stdout. Auf diese Weise vermasseln Sie sie nicht, wenn Sie von einer Funktion aufgerufen werden, die sie verwendet print.
mgold
4
@Jan: Deaktiviert buffering=0die Pufferung (dies kann die Leistung beeinträchtigen (10-100 Mal)). buffering=1Aktiviert die Zeilenpufferung, sodass Sie sie tail -ffür eine zeilenorientierte Ausgabe verwenden können.
JFS
41
@mgold oder Sie können verwenden sys.stdout = sys.__stdout__, um es zurück zu bekommen.
Clemtoy
176

In Python 3.4 gibt es eine contextlib.redirect_stdout()Funktion :

from contextlib import redirect_stdout

with open('help.txt', 'w') as f:
    with redirect_stdout(f):
        print('it now prints to `help.text`')

Es ist ähnlich wie:

import sys
from contextlib import contextmanager

@contextmanager
def redirect_stdout(new_target):
    old_target, sys.stdout = sys.stdout, new_target # replace sys.stdout
    try:
        yield new_target # run some code with the replaced stdout
    finally:
        sys.stdout = old_target # restore to the previous value

Dies kann in früheren Python-Versionen verwendet werden. Die letztere Version ist nicht wiederverwendbar . Es kann auf Wunsch auch hergestellt werden.

Das stdout wird auf der Ebene der Dateideskriptoren nicht umgeleitet, z.

import os
from contextlib import redirect_stdout

stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, redirect_stdout(f):
    print('redirected to a file')
    os.write(stdout_fd, b'not redirected')
    os.system('echo this also is not redirected')

b'not redirected'und 'echo this also is not redirected'werden nicht in die output.txtDatei umgeleitet .

Zum Umleiten auf Dateideskriptorebene kann Folgendes os.dup2()verwendet werden:

import os
import sys
from contextlib import contextmanager

def fileno(file_or_fd):
    fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)()
    if not isinstance(fd, int):
        raise ValueError("Expected a file (`.fileno()`) or a file descriptor")
    return fd

@contextmanager
def stdout_redirected(to=os.devnull, stdout=None):
    if stdout is None:
       stdout = sys.stdout

    stdout_fd = fileno(stdout)
    # copy stdout_fd before it is overwritten
    #NOTE: `copied` is inheritable on Windows when duplicating a standard stream
    with os.fdopen(os.dup(stdout_fd), 'wb') as copied: 
        stdout.flush()  # flush library buffers that dup2 knows nothing about
        try:
            os.dup2(fileno(to), stdout_fd)  # $ exec >&to
        except ValueError:  # filename
            with open(to, 'wb') as to_file:
                os.dup2(to_file.fileno(), stdout_fd)  # $ exec > to
        try:
            yield stdout # allow code to be run with the redirected stdout
        finally:
            # restore stdout to its previous value
            #NOTE: dup2 makes stdout_fd inheritable unconditionally
            stdout.flush()
            os.dup2(copied.fileno(), stdout_fd)  # $ exec >&copied

Das gleiche Beispiel funktioniert jetzt, wenn stdout_redirected()anstelle von redirect_stdout():

import os
import sys

stdout_fd = sys.stdout.fileno()
with open('output.txt', 'w') as f, stdout_redirected(f):
    print('redirected to a file')
    os.write(stdout_fd, b'it is redirected now\n')
    os.system('echo this is also redirected')
print('this is goes back to stdout')

Die Ausgabe, die zuvor auf stdout gedruckt wurde, wird jetzt angezeigt, output.txtsolange der stdout_redirected()Kontextmanager aktiv ist.

Hinweis: stdout.flush()Leert keine C stdio-Puffer in Python 3, in denen E / A direkt in read()/ write()system-Aufrufen implementiert ist . Um alle offenen C stdio-Ausgabestreams zu leeren, können Sie libc.fflush(None)explizit aufrufen, wenn eine C-Erweiterung stdio-basierte E / A verwendet:

try:
    import ctypes
    from ctypes.util import find_library
except ImportError:
    libc = None
else:
    try:
        libc = ctypes.cdll.msvcrt # Windows
    except OSError:
        libc = ctypes.cdll.LoadLibrary(find_library('c'))

def flush(stream):
    try:
        libc.fflush(None)
        stream.flush()
    except (AttributeError, ValueError, IOError):
        pass # unsupported

Sie können stdoutParameter verwenden, um andere Streams umzuleiten, nicht nur sys.stdoutzum Zusammenführen sys.stderrund sys.stdout:

def merged_stderr_stdout():  # $ exec 2>&1
    return stdout_redirected(to=sys.stdout, stdout=sys.stderr)

Beispiel:

from __future__ import print_function
import sys

with merged_stderr_stdout():
     print('this is printed on stdout')
     print('this is also printed on stdout', file=sys.stderr)

Hinweis: stdout_redirected()Mischt gepufferte E / A ( sys.stdoutnormalerweise) und ungepufferte E / A (Operationen an Dateideskriptoren direkt). Vorsicht, es könnte sein , Pufferung Probleme .

Um zu antworten, Ihre Bearbeitung: Sie könnten verwenden, python-daemonum Ihr Skript zu dämonisieren und loggingModul (wie von @ erikb85 vorgeschlagen ) anstelle von printAnweisungen zu verwenden und lediglich stdout für Ihr lang laufendes Python-Skript umzuleiten, das Sie nohupjetzt verwenden.

jfs
quelle
3
stdout_redirectedist hilfreich. Beachten Sie, dass dies in Doctests nicht funktioniert, da der spezielle SpoofOutHandler, den Doctest zum Ersetzen sys.stdoutverwendet, kein filenoAttribut hat.
Chris Johnson
@ ChrisJohnson: Wenn es nicht ausgelöst wird, ValueError("Expected a file (`.fileno()`) or a file descriptor")ist es ein Fehler. Bist du sicher, dass es das nicht erhöht?
JFS
Es wirft diesen Fehler auf, was es innerhalb eines Doktests nicht verwendbar macht. Um Ihre Funktion innerhalb eines Doctests zu verwenden, müssen Sie angeben, doctest.sys.__stdout__wo wir normalerweise verwenden würden sys.stdout. Dies ist kein Problem mit Ihrer Funktion, sondern nur eine für doctest erforderliche Anpassung, da sie stdout durch ein Objekt ersetzt, das nicht alle Attribute aufweist, die eine echte Datei hätte.
Chris Johnson
stdout_redirected()Wenn Sie einen stdoutParameter haben, können Sie ihn auf setzen, sys.__stdout__wenn Sie das ursprüngliche Python-Standardout umleiten möchten (das sollte .fileno()in den meisten Fällen gültig sein ). Es tut nichts für den Strom, sys.stdoutwenn sie unterschiedlich sind. Nicht benutzen doctest.sys; es ist versehentlich verfügbar.
JFS
Dies funktioniert wirklich gut, dh stdout und stderr zu einem fd umleiten: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok
91

Sie können dies zu viel besser versuchen

import sys

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

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

sys.stdout = Logger("yourlogfilename.txt")
print "Hello world !" # this is should be saved in yourlogfilename.txt
Yuda Prawira
quelle
Irgendwelche Vorschläge für Rohrleitungen zu loggeroder syslog?
dsummersl
Wenn Sie eine Datei bearbeiten möchten, ist dies nicht sehr nützlich. Wie auch immer +1 für den schönen Trick
aIKid
10
Dies hat Konsequenzen für Code, der davon ausgeht, dass sys.stdout ein vollwertiges Dateiobjekt mit Methoden wie fileno () ist (das Code in der Python-Standardbibliothek enthält). Ich würde eine __getattr __ (self, attr) -Methode zu der hinzufügen, die die Attributsuche auf self.terminal verschiebt. def __getattr__(self, attr): return getattr(self.terminal, attr)
Peabody
4
Sie müssen def flush(self):der Klasse auch eine Methode hinzufügen Logger.
Loretoparisi
1
@loretoparisi aber was geht eigentlich in der Methode, die Sie erstellen?
Elkshadow5
28

Die anderen Antworten betrafen nicht den Fall, in dem gegabelte Prozesse Ihr neues Standard freigeben sollen.

Das zu tun:

from os import open, close, dup, O_WRONLY

old = dup(1)
close(1)
open("file", O_WRONLY) # should open on 1

..... do stuff and then restore

close(1)
dup(old) # should dup to 1
close(old) # get rid of left overs
Yam Marcovic
quelle
3
man muss das 'w'-Attribut durch os.O_WRONLY | os.O_CREATE ersetzen ... kann keine Strings in die "os" -Befehle senden!
Ch'marr
3
Fügen Sie sys.stdout.flush()vor der close(1)Anweisung ein ein, um sicherzustellen, dass die Umleitungsdatei 'file'die Ausgabe erhält. Sie können auch eine tempfile.mkstemp()Datei anstelle von verwenden 'file'. Und seien Sie vorsichtig, dass keine anderen Threads ausgeführt werden, die das erste Dateihandle des Betriebssystems nach dem, os.close(1)aber vor dem 'file'Öffnen stehlen können, um das Handle zu verwenden.
Alex Robinson
2
seine os.O_WRONLY | os.O_CREAT ... da ist kein E.
Jeff Sheffield
@ Ch'marr Es ist O_CREAT, nicht O_CREATE.
quant_dev
28

Zitiert aus PEP 343 - Die "with" -Anweisung (hinzugefügte Importanweisung):

Stdout vorübergehend umleiten:

import sys
from contextlib import contextmanager
@contextmanager
def stdout_redirected(new_stdout):
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield None
    finally:
        sys.stdout = save_stdout

Wird wie folgt verwendet:

with open(filename, "w") as f:
    with stdout_redirected(f):
        print "Hello world"

Dies ist natürlich nicht threadsicher, aber es wird auch nicht manuell getanzt. In Single-Thread-Programmen (zum Beispiel in Skripten) ist dies eine beliebte Methode, um Dinge zu tun.

Gerli
quelle
1
+1. Hinweis: Es funktioniert nicht für Unterprozesse, z os.system('echo not redirected'). Meine Antwort zeigt, wie man eine solche Ausgabe umleitet
jfs
Ab Python 3.4 gibt es redirect_stdoutincontextlib
Walter Tross
12
import sys
sys.stdout = open('stdout.txt', 'w')
Cat Plus Plus
quelle
3

Hier ist eine Variation der Antwort von Yuda Prawira :

  • implementieren flush()und alle Dateiattribute
  • schreibe es als Kontextmanager
  • erfassen stderrauch

.

import contextlib, sys

@contextlib.contextmanager
def log_print(file):
    # capture all outputs to a log file while still printing it
    class Logger:
        def __init__(self, file):
            self.terminal = sys.stdout
            self.log = file

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

        def __getattr__(self, attr):
            return getattr(self.terminal, attr)

    logger = Logger(file)

    _stdout = sys.stdout
    _stderr = sys.stderr
    sys.stdout = logger
    sys.stderr = logger
    try:
        yield logger.log
    finally:
        sys.stdout = _stdout
        sys.stderr = _stderr


with log_print(open('mylogfile.log', 'w')):
    print('hello world')
    print('hello world on stderr', file=sys.stderr)

# you can capture the output to a string with:
# with log_print(io.StringIO()) as log:
#   ....
#   print('[captured output]', log.getvalue())
damio
quelle
2

Basierend auf dieser Antwort: https://stackoverflow.com/a/5916874/1060344 , habe ich auf andere Weise herausgefunden, welche ich in einem meiner Projekte verwende. Für alles, was Sie ersetzen sys.stderroder sys.stdoutdurch was Sie ersetzen , müssen Sie sicherstellen, dass der Ersatz mit der fileSchnittstelle übereinstimmt , insbesondere wenn Sie dies tun, da stderr / stdout in einer anderen Bibliothek verwendet wird, die nicht unter Ihrer Kontrolle steht. Diese Bibliothek verwendet möglicherweise andere Methoden für Dateiobjekte.

Schauen Sie sich diesen Weg an, auf dem ich immer noch alles stderr / stdout (oder eine beliebige Datei) ausführen lasse und die Nachricht mithilfe der Python-Protokollierungsfunktion an eine Protokolldatei sende (aber Sie können damit wirklich alles tun):

class FileToLogInterface(file):
    '''
    Interface to make sure that everytime anything is written to stderr, it is
    also forwarded to a file.
    '''

    def __init__(self, *args, **kwargs):
        if 'cfg' not in kwargs:
            raise TypeError('argument cfg is required.')
        else:
            if not isinstance(kwargs['cfg'], config.Config):
                raise TypeError(
                    'argument cfg should be a valid '
                    'PostSegmentation configuration object i.e. '
                    'postsegmentation.config.Config')
        self._cfg = kwargs['cfg']
        kwargs.pop('cfg')

        self._logger = logging.getlogger('access_log')

        super(FileToLogInterface, self).__init__(*args, **kwargs)

    def write(self, msg):
        super(FileToLogInterface, self).write(msg)
        self._logger.info(msg)
vaidik
quelle
2

Sie benötigen einen Terminal-Multiplexer wie tmux oder GNU screen

Ich bin überrascht, dass ein kleiner Kommentar von Ryan Amos zu der ursprünglichen Frage die einzige Erwähnung einer Lösung ist, die allen anderen angebotenen weit vorzuziehen ist, egal wie klug die Python-Tricks sind und wie viele positive Stimmen sie erhalten haben. Neben Ryans Kommentar ist tmux eine gute Alternative zum GNU-Bildschirm.

Das Prinzip ist jedoch dasselbe: Wenn Sie jemals einen Terminaljob laufen lassen möchten, während Sie sich abmelden, gehen Sie ins Café, um ein Sandwich zu essen, gehen Sie ins Badezimmer, gehen Sie nach Hause (usw.) und verbinden Sie sich später wieder mit Ihrem Terminal-Sitzung von überall oder von jedem Computer aus, als wären Sie nie weg gewesen, Terminal-Multiplexer sind die Antwort. Stellen Sie sich diese als VNC oder Remotedesktop für Terminalsitzungen vor. Alles andere ist eine Problemumgehung. Als Bonus haben Sie nicht die letzten 18 Stunden der Verarbeitung verloren, wenn der Chef und / oder Partner hereinkommt und Sie versehentlich Ihr Terminalfenster anstelle Ihres Browserfensters mit seinem zwielichtigen Inhalt bei gedrückter Strg-Taste drücken !

Duncan
quelle
4
während es eine gute Antwort für den Teil der Frage ist, der nach der Bearbeitung erschien; es beantwortet nicht die Frage im Titel (die meisten Leute kommen hierher von Google für den Titel)
jfs
0

Programme, die in anderen Sprachen (z. B. C) geschrieben wurden, müssen spezielle Magie (Double-Forking genannt) ausführen, um sich ausdrücklich vom Terminal zu lösen (und Zombie-Prozesse zu verhindern). Daher denke ich, dass die beste Lösung darin besteht, sie zu emulieren.

Ein Plus bei der erneuten Ausführung Ihres Programms ist, dass Sie Umleitungen in der Befehlszeile auswählen können, z /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Weitere Informationen finden Sie in diesem Beitrag: Was ist der Grund für die Ausführung einer Doppelgabelung beim Erstellen eines Dämons?

jpaugh
quelle
Eh ... kann nicht sagen, dass ich ein Fan von Prozessen bin, die ihre eigene Doppelgabelung verwalten. Es ist eine so verbreitete Redewendung und so einfach, falsch zu codieren, wenn Sie nicht vorsichtig sind. Schreiben Sie Ihren Prozess besser so, dass er im Vordergrund ausgeführt wird, und verwenden Sie einen Task-Manager für den Systemhintergrund ( systemd, upstart) oder ein anderes Dienstprogramm ( daemon(1)), um die Gabelungsplatte zu handhaben.
Lucretiel