Rediriger stdout vers un fichier en Python?

comment rediriger stdout vers un fichier arbitraire en Python?

quand un script Python de longue durée (par ex. g, web application) est démarré à l'intérieur de la session ssh et backgounded, et la session ssh est fermé, l'application va soulever IOError et échouer au moment où il essaie d'écrire à stdout. Je devais trouver un moyen de faire l'application et les modules sortie à un dossier plutôt que stdout pour empêcher l'échec dû à IOError. Actuellement, j'emploie nohup pour rediriger sortie vers un fichier, et cela fait le travail, mais je me demandais s'il y avait un moyen de le faire sans utiliser nohup, par curiosité.

j'ai déjà essayé sys.stdout = open('somefile', 'w') , mais cela ne semble pas empêcher certains modules externes de continuer à sortir vers le terminal (ou peut-être que la ligne sys.stdout = ... n'a pas tiré du tout). Je sais que cela devrait fonctionner à partir de scripts plus simples que j'ai testés, mais je n'ai pas encore eu le temps de tester sur une application web.

235
demandé sur Eric Leschinski 2011-01-13 03:51:00

10 réponses

si vous voulez faire la redirection dans le script Python, mettez sys.stdout à un objet file does the trick:

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

une méthode beaucoup plus courante est d'utiliser la redirection du shell lors de l'exécution (même pour Windows et Linux):

$ python foo.py > file
304
répondu marcog 2011-01-13 00:53:28

il y a contextlib.redirect_stdout() fonction en 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`')

il est similaire à:

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

qui peut être utilisé sur les versions antérieures de Python. Cette dernière version n'est pas réutilisable . Il peut être fait un si désiré.

il ne redirige pas le stdout au niveau des descripteurs de fichier par exemple:

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' et 'echo this also is not redirected' ne sont pas redirigés vers le fichier output.txt .

pour rediriger au niveau du descripteur de fichier, os.dup2() pourrait être utilisé:

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

le même exemple fonctionne maintenant si stdout_redirected() est utilisé au lieu de 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')

la sortie qui était auparavant imprimée sur stdout va maintenant à output.txt aussi longtemps que stdout_redirected() gestionnaire de contexte est actif.

Note: stdout.flush() ne chasse pas Les tampons Studio sur Python 3 où I / O est implémenté directement sur les appels système read() / write() . Pour vider tous les flux de sortie de studio ouverts, vous pouvez appeler libc.fflush(None) explicitement si une extension C utilise l'E/S basé sur 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

vous pouvez utiliser le paramètre stdout pour rediriger d'autres flux, pas seulement sys.stdout par exemple, pour fusionner sys.stderr et sys.stdout :

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

Exemple:

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)

Note: stdout_redirected() mélange I/O tamponné ( sys.stdout habituellement) et I/O Non tamponné (opérations sur les descripteurs de fichier directement). Méfiez-vous, il pourrait y avoir tampon questions .

pour répondre À votre montage: vous pouvez utiliser python-daemon à daemonize votre script et utiliser logging (module @erikb85 suggéré ) au lieu des instructions print et de simplement rediriger stdout pour votre script Python que vous exécutez en utilisant nohup maintenant.

125
répondu jfs 2017-05-23 12:26:43

vous pouvez essayer cela aussi beaucoup mieux

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
83
répondu Yuda Prawira 2013-05-10 00:35:10

les autres réponses ne couvrent pas le cas où vous voulez des processus fourchés pour partager votre nouvelle stdout.

pour faire cela:

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
29
répondu Yam Marcovic 2014-03-15 00:04:01

Cité PEP 343 -- La "avec la" Déclaration (ajoutée à l'importation de déclaration):

Rediriger stdout temporairement:

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

utilisé comme suit:

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

ce n'est pas sans fil, bien sûr, mais ni l'un ni l'autre ne fait cette même danse manuellement. En monothread programmes (par exemple dans les scripts) c'est une façon populaire de faire les choses.

21
répondu Gerli 2018-06-06 13:07:10
import sys
sys.stdout = open('stdout.txt', 'w')
10
répondu Cat Plus Plus 2011-01-13 00:52:51

basé sur cette réponse: https://stackoverflow.com/a/5916874/1060344 , voici une autre façon que j'ai trouvé que j'utilise dans un de mes projets. Pour tout ce avec quoi vous remplacez sys.stderr ou sys.stdout , vous devez vous assurer que le remplacement est conforme à l'interface file , surtout si c'est quelque chose que vous faites parce que stderr/stdout sont utilisés dans une autre bibliothèque qui n'est pas sous votre contrôle. Cette bibliothèque peut utiliser d'autres méthodes de fichier objet.

regardez de cette façon où je laisse tout faire pour stderr / stdout (ou n'importe quel fichier) et j'envoie aussi le message à un fichier journal en utilisant la fonction de journalisation de Python (mais vous pouvez vraiment faire n'importe quoi avec ça):

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
répondu vaidik 2017-05-23 11:33:26

vous avez besoin d'un multiplexeur de terminal comme tmux ou GNU screen

je suis surpris qu'un petit commentaire de Ryan Amos à la question originale soit la seule mention d'une solution de loin préférable à toutes les autres sur l'offre, peu importe comment la ruse de python peut être et combien de upvotes ils ont reçu. Suite au commentaire de Ryan, tmux est une bonne alternative à GNU screen.

mais le le principe est le même: si jamais vous vous retrouvez à vouloir quitter un terminal en cours d'exécution pendant que vous vous déconnectez, rendez-vous au café pour un sandwich, pop à la salle de bains, rentrer à la maison (etc) et puis plus tard, reconnectez à votre session de terminal à partir de n'importe où ou n'importe quel ordinateur comme si vous n'aviez jamais été absent, multiplexeurs de terminal sont la réponse. Pensez à eux comme VNC ou bureau distant pour les sessions de terminal. Tout le reste est une solution de contournement. En prime, quand le patron et/ou le partenaire entre et vous par inadvertance ctrl-w / cmd-w votre fenêtre de terminal au lieu de votre fenêtre de navigateur avec son contenu douteux, vous n'aurez pas perdu les 18 dernières heures de traitement!

2
répondu duncan 2017-08-23 05:17:02

les programmes écrits dans d'autres langues (par exemple C) doivent faire de la magie spéciale (appelée double-bifurcation) expressément pour se détacher du terminal (et pour empêcher les processus Zombies). Donc, je pense que la meilleure solution est de les imiter.

un plus de ré-exécuter votre programme est, vous pouvez choisir des redirections sur la ligne de commande, par exemple /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

voir ce post pour plus d'informations: Quelle est la raison pour effectuer une double fourchette lors de la création d'un démon?

1
répondu jpaugh 2017-05-23 12:10:54

voici une variation de Yuda Prawira réponse:

  • implement flush() et tous les attributs de fichier
  • écrire comme un contextmanager
  • capture stderr aussi

.

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
répondu damio 2018-09-05 13:41:11