Reindirizzare lo stdout su un file in Python?


315

Come reindirizzare stdout a un file arbitrario in Python?

Quando uno script Python di lunga durata (ad esempio, un'applicazione web) viene avviato dall'interno della sessione ssh e viene eseguito il backgounding, e la sessione ssh viene chiusa, l'applicazione genererà IOError e fallirà nel momento in cui tenta di scrivere su stdout. Avevo bisogno di trovare un modo per rendere l'applicazione e i moduli in un file piuttosto che stdout per evitare guasti dovuti a IOError. Attualmente, utilizzo nohup per reindirizzare l'output su un file, e questo fa il lavoro, ma mi chiedevo se c'era un modo per farlo senza usare nohup, per curiosità.

Ho già provato sys.stdout = open('somefile', 'w'), ma questo non sembra impedire ad alcuni moduli esterni di essere inviati al terminale (o forse la sys.stdout = ...linea non si è affatto attivata). So che dovrebbe funzionare con script più semplici su cui ho provato, ma non ho ancora avuto il tempo di testare ancora su un'applicazione web.


8
Non è proprio una cosa pitone, è una funzione shell. script.p > file
Esegui la

Attualmente risolvo il problema usando nohup, ma pensavo che potesse esserci qualcosa di più intelligente ...

1
@foxbunny: nohup? Perché semplicemente someprocess | python script.py? Perché coinvolgere nohup?
S.Lott

3
Riscrivi le printistruzioni per applicare il loggingmodulo dallo stdlib. Quindi puoi reindirizzare l'output ovunque, avere il controllo sulla quantità di output che desideri ecc. Nella maggior parte dei casi il codice di produzione non dovrebbe printma log.
erikbwork,

2
Forse una soluzione migliore per questo problema è il comando screen, che salverà la tua sessione bash e ti permetterà di accedervi da diverse esecuzioni.
Ryan Amos,

Risposte:


404

Se vuoi fare il reindirizzamento all'interno dello script Python, l'impostazione sys.stdoutsu un oggetto file fa il trucco:

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

Un metodo molto più comune è utilizzare il reindirizzamento della shell durante l'esecuzione (lo stesso su Windows e Linux):

$ python foo.py > file


7
Non funziona from sys import stdout, forse perché crea una copia locale. Inoltre puoi usarlo con with, ad es with open('file', 'w') as sys.stdout: functionThatPrints(). Ora puoi implementare functionThatPrints()usando le normali printistruzioni.
mgold

41
È meglio conservare una copia locale, in stdout = sys.stdoutmodo da poterlo rimettere quando hai finito sys.stdout = stdout. In questo modo se vieni chiamato da una funzione che usi printnon li rovini.
mgold

4
@Jan: buffering=0disabilita il buffering (può influire negativamente sulle prestazioni (10-100 volte)). buffering=1abilita il buffering di linea in modo da poterlo utilizzare tail -fper un output orientato alla linea.
jfs

41
@mgold oppure puoi usarlo sys.stdout = sys.__stdout__per riaverlo .
Clemtoy,

176

C'è una contextlib.redirect_stdout()funzione 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`')

È simile a:

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

che può essere utilizzato su versioni precedenti di Python. L'ultima versione non è riutilizzabile . Può essere fatto uno se lo si desidera.

Non reindirizza lo stdout a livello di descrittori di file, ad esempio:

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'e 'echo this also is not redirected'non vengono reindirizzati al output.txtfile.

Per reindirizzare a livello di descrittore di file, è os.dup2()possibile utilizzare:

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

Lo stesso esempio funziona ora se stdout_redirected()viene utilizzato invece di 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')

L'output precedentemente stampato su stdout ora continua fino output.txta quando il stdout_redirected()gestore di contesto è attivo.

Nota: stdout.flush()non svuota i buffer C stdio su Python 3 dove l'I / O è implementato direttamente sulle chiamate read()/ write()system. Per svuotare tutti i flussi di output C stdio aperti, è possibile chiamare libc.fflush(None)esplicitamente se alcune estensioni C utilizzano I / O basati su stdio:

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

È possibile utilizzare il stdoutparametro per reindirizzare altri flussi, non solo sys.stdoutad esempio per unire sys.stderre sys.stdout:

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

Esempio:

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)

Nota: stdout_redirected()mescola I / O con buffer (in sys.stdoutgenere) e I / O senza buffer (operazioni sui descrittori di file direttamente). Attenzione, potrebbero esserci problemi di buffering .

Per rispondere, la tua modifica: potresti usare python-daemonper demonizzare il tuo script e usare il loggingmodulo (come suggerito da @ erikb85 ) invece di printistruzioni e semplicemente reindirizzare stdout per il tuo script Python di lunga durata che esegui nohupora.


3
stdout_redirectedè utile Ricorda che questo non funziona all'interno dei doctest, poiché lo speciale SpoofOutgestore che doctest usa per sostituire sys.stdoutnon ha un filenoattributo.
Chris Johnson,

@ChrisJohnson: se non si alza ValueError("Expected a file (`.fileno()`) or a file descriptor"), è un bug. Sei sicuro che non lo sollevi?
jfs,

Fa sorgere questo errore, che è ciò che lo rende non utilizzabile all'interno di un doctest. Per usare la tua funzione all'interno di un doctest, sembra necessario specificare doctest.sys.__stdout__dove useremmo normalmente sys.stdout. Questo non è un problema con la tua funzione, ma solo una sistemazione richiesta per doctest poiché sostituisce stdout con un oggetto che non ha tutti gli attributi di un vero file.
Chris Johnson,

stdout_redirected()ha un stdoutparametro, è possibile impostarlo su sys.__stdout__se si desidera reindirizzare lo stdout originale di Python (che dovrebbe avere un valore valido .fileno()nella maggior parte dei casi). Non fa nulla per la corrente sys.stdoutse sono diversi. Non usare doctest.sys; è disponibile per caso.
jfs,

Funziona davvero bene, ovvero reindirizzare stdout e stderr a un fd: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok,

91

puoi provare questo troppo meglio

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

Qualche suggerimento per il piping a loggero syslog?
dsummersl

Se vuoi modificare un file, questo non è molto utile. Comunque +1 per il bel trucco
aIKid

10
Ciò avrà conseguenze per il codice che presuppone che sys.stdout sia un oggetto file completo con metodi come fileno () (che include il codice nella libreria standard di Python). Aggiungerei un metodo __getattr __ (self, attr) a quello che difende la ricerca degli attributi in self.terminal. def __getattr__(self, attr): return getattr(self.terminal, attr)
peabody,

4
Devi aggiungere anche il def flush(self):metodo alla classe Logger.
Loretoparisi,

1
@loretoparisi ma cosa succede effettivamente nel metodo che crei?
elkshadow5

28

Le altre risposte non hanno riguardato il caso in cui si desidera che i processi biforcuti condividano il nuovo stdout.

Fare quello:

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

3
bisogna sostituire l'attributo 'w' con, os.O_WRONLY | os.O_CREATE ... non è possibile inviare stringhe nei comandi "os"!
Ch'marr,

3
Inserire una sys.stdout.flush()prima close(1)dell'istruzione per assicurarsi che il 'file'file di reindirizzamento ottenga l'output. Inoltre, è possibile utilizzare un tempfile.mkstemp()file al posto di 'file'. E fai attenzione a non avere altri thread in esecuzione che possono rubare il primo handle di file del sistema operativo dopo il os.close(1)ma prima che 'file'sia aperto per usare l'handle.
Alex Robinson,

2
suo os.O_WRONLY | os.O_CREAT ... non c'è E lì.
Jeff Sheffield,


@ Ch'marr È O_CREAT, non O_CREATE.
quant_dev,

28

Citato da PEP 343 - L'istruzione "with" (aggiunta dichiarazione di importazione):

Reindirizzare temporaneamente stdout:

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

Usato come segue:

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

Questo non è sicuro per i thread, ovviamente, ma non sta nemmeno facendo questa stessa danza manualmente. Nei programmi a thread singolo (ad esempio negli script) è un modo popolare di fare le cose.


1
+1. Nota: non funziona per i sottoprocessi, ad es os.system('echo not redirected'). La mia risposta mostra come reindirizzare tale output
jfs

a partire da Python 3.4 è redirect_stdoutincontextlib
Walter Tross


3

Ecco una variante della risposta di Yuda Prawira :

  • implementare flush()e tutti gli attributi del file
  • scrivilo come un gestore di contesto
  • cattura stderranche

.

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

2

Sulla base di questa risposta: https://stackoverflow.com/a/5916874/1060344 , ecco un altro modo in cui ho capito quale utilizzo in uno dei miei progetti. Per qualunque cosa tu sostituisca sys.stderro sys.stdoutcon, devi assicurarti che la sostituzione sia conformefile all'interfaccia, specialmente se questo è qualcosa che stai facendo perché stderr / stdout sono usati in qualche altra libreria che non è sotto il tuo controllo. Quella libreria potrebbe utilizzare altri metodi di oggetto file.

Dai un'occhiata a questo modo in cui ho ancora lasciato tutto andare stderr / stdout (o qualsiasi altro file) e invia anche il messaggio a un file di registro utilizzando la funzione di registrazione di Python (ma puoi davvero fare qualsiasi cosa con questo):

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

È necessario un multiplexer terminale come tmux o schermo GNU

Sono sorpreso che un piccolo commento di Ryan Amos alla domanda originale sia l'unica menzione di una soluzione di gran lunga preferibile a tutti gli altri offerti, non importa quanto intelligente possa essere l'inganno del pitone e quanti voti hanno ricevuto. Oltre al commento di Ryan, tmux è una bella alternativa allo schermo GNU.

Ma il principio è lo stesso: se ti ritrovi a voler lasciare un lavoro terminale mentre ti disconnetti, vai al bar per un panino, vai in bagno, vai a casa (ecc.) E poi, riconnettiti al tuo sessione terminale da qualsiasi luogo o computer come se non fossi mai stato via, i multiplexer terminali sono la risposta. Pensali come VNC o desktop remoto per sessioni terminali. Qualsiasi altra cosa è una soluzione alternativa. Come bonus, quando il capo e / o il partner entrano e inavvertitamente ctrl-w / cmd-w la finestra del terminale invece della finestra del browser con il suo contenuto ingannevole, non avrai perso le ultime 18 ore di elaborazione !


4
mentre è una buona risposta per la parte della domanda apparso dopo la modifica; non risponde alla domanda nel titolo (la maggior parte delle persone viene qui da Google per il titolo)
jfs

0

I programmi scritti in altre lingue (ad es. C) devono fare magie speciali (chiamate double-biforcazione) espressamente per staccarsi dal terminale (e per prevenire i processi di zombi). Quindi, penso che la soluzione migliore sia emularli.

Un vantaggio di rieseguire il programma è che puoi scegliere i reindirizzamenti sulla riga di comando, ad es /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Vedi questo post per maggiori informazioni: Qual è il motivo per eseguire un doppio fork durante la creazione di un demone?


Eh ... non posso dire che sono un fan dei processi che gestiscono il loro doppio fork. È un linguaggio così comune, e così facile scrivere un codice sbagliato se non stai attento. Meglio scrivere il processo da eseguire in primo piano e utilizzare un task manager in background del sistema ( systemd, upstart) o un'altra utility ( daemon(1)) per gestire la piastra di forking.
Lucretiel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.