it-swarm.com.de

Stdout in eine Datei in Python umleiten?

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

Wenn ein lang laufendes Python -Skript (z. B. eine Webanwendung) aus der SSH-Sitzung heraus gestartet und zurückbegründet wird und die SSH-Sitzung geschlossen wird, löst die Anwendung IOError aus und schlägt fehl, sobald versucht wird, darauf zu schreiben stdout. Ich musste eine Möglichkeit finden, die Anwendung und die Module in eine Datei anstatt in stdout auszugeben, um einen Ausfall aufgrund von IOError zu verhindern. Momentan 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 Nohup zu tun.

Ich habe bereits sys.stdout = open('somefile', 'w') ausprobiert, aber dies scheint nicht zu verhindern, dass einige externe Module weiterhin auf dem Terminal ausgegeben werden (oder die sys.stdout = ... -Leitung hat überhaupt nicht ausgelöst). Ich weiß, dass es mit einfacheren Skripten funktionieren sollte, auf denen ich getestet habe, aber ich hatte auch noch keine Zeit, auf einer Webanwendung zu testen.

273
user234932

Wenn Sie die Umleitung innerhalb des Skripts Python ausführen möchten, setzen Sie sys.stdout auf ein Dateiobjekt.

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

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

$ python foo.py > file
354
marcog

Es gibt contextlib.redirect_stdout() function in Python 3.4:

_from contextlib import redirect_stdout

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

Das ist vergleichbar mit:

_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 einer gemacht werden.

Die Standardausgabe wird nicht auf Dateideskriptorebene 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 Datei _output.txt_ umgeleitet.

Um auf Dateideskriptorebene umzuleiten, kann 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
_

Dasselbe Beispiel funktioniert jetzt, wenn stdout_redirected() anstelle von redirect_stdout() verwendet wird:

_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 an _output.txt_ gesendet, solange der Kontextmanager stdout_redirected() aktiv ist.

Hinweis: stdout.flush() löscht C stdio-Puffer in Python 3 nicht, wobei E/A direkt in read()/write() Systemaufrufen implementiert wird. 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 den Parameter stdout verwenden, um andere Streams umzuleiten, nicht nur _sys.stdout_, z. B. um _sys.stderr_ und _sys.stdout_ zusammenzuführen:

_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 in der Regel gepufferte E/A (_sys.stdout_) und ungepufferte E/A (Operationen auf Dateideskriptoren direkt). Achtung, es könnte PufferungProbleme geben.

Zur Beantwortung können Sie python-daemon verwenden, um Ihr Skript zu dämonisieren und das Modul logging (als @ erikb85 vorgeschlagen ) anstelle von print-Anweisungen zu verwenden und lediglich stdout umzuleiten für Ihr lang laufendes Python Skript, das Sie jetzt mit Nohup ausführen.

140
jfs

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
88
Yuda Prawira

Die anderen Antworten deckten nicht den Fall ab, in dem gespaltene Prozesse Ihre neue Standardausgabe teilen 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
28
Yam Marcovic

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"

Das ist natürlich nicht threadsicher, aber auch nicht manuell. In Single-Thread-Programmen (zum Beispiel in Skripten) ist dies eine beliebte Methode.

26
Gerli
import sys
sys.stdout = open('stdout.txt', 'w')
11
Cat Plus Plus

Basierend auf dieser Antwort: https://stackoverflow.com/a/5916874/1060344 , hier ist eine andere Möglichkeit, die ich in einem meiner Projekte verwendet habe. Für alles, was Sie durch sys.stderr oder sys.stdout ersetzen, müssen Sie sicherstellen, dass das Ersetzen der Schnittstelle file entspricht, insbesondere, wenn Sie dies tun, weil in einigen Fällen stderr/stdout verwendet werden andere Bibliothek, die nicht unter Ihrer Kontrolle ist. Diese Bibliothek verwendet möglicherweise andere Methoden des Dateiobjekts.

Sehen Sie sich auf diese Weise an, wo ich immer noch alles stderr/stdout (oder eine beliebige Datei) ausführen lasse, und senden Sie die Nachricht mithilfe der Python-Protokollierungsfunktion an eine Protokolldatei (aber damit können Sie 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)
2
vaidik

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

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 im Angebot weit vorzuziehen ist, egal wie clever die python -Täuschung sein mag und wie viele positive Stimmen sie haben habe erhalten. Nach Ryans Kommentar ist tmux eine gute Alternative zu GNU screen.

Das Prinzip ist jedoch dasselbe: Wenn Sie während des Abmeldevorgangs einmal einen Terminaljob laufen lassen möchten, gehen Sie ins Café, gehen Sie ins Badezimmer, gehen Sie nach Hause (usw.) und stellen Sie später die Verbindung zu Ihrem wieder her Terminal-Sitzung von überall oder von jedem Computer aus, als wären Sie nie weg, sind Terminal-Multiplexer die Antwort. Stellen Sie sich diese als VNC oder Remote-Desktop für Terminalsitzungen vor. Alles andere ist eine Problemumgehung. Als Bonus haben Sie nicht die letzten 18 Stunden der Bearbeitung verloren, wenn der Chef und/oder Partner hereinkommt und Sie versehentlich das Terminalfenster anstelle des Browserfensters mit seinen zweifelhaften Inhalten drücken !

2
duncan

Hier ist eine Variation von Yuda Prawira Antwort:

  • implementieren Sie flush() und alle Dateiattribute
  • schreibe es als contextmanager
  • erfassen stderr auch

.

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())
1
damio

Programme, die in anderen Sprachen (z. B. C) geschrieben wurden, müssen besondere magische Aktionen ausführen (Double-Forking), um sich ausdrücklich vom Terminal zu lösen (und Zombie-Prozesse zu verhindern). Ich denke, die beste Lösung ist, sie zu emulieren.

Ein Pluspunkt bei der erneuten Ausführung Ihres Programms ist, dass Sie in der Befehlszeile Umleitungen 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, warum beim Erstellen eines Daemons ein Double Fork ausgeführt wird?

0
jpaugh

@ marcog

Die zweite Option ist nur dann sinnvoll, wenn das Skript sofort ausgeführt wird. Oder wenn das Skript vollständig ausgeführt wird, wird die Ausgabe in diese Datei geschrieben und es sollten keine Endlosschleifen vorhanden sein (optimal). Beste Lösung, wenn es sich um ein einfaches Skript handelt.

0
yunus