web-dev-qa-db-fra.com

Rediriger la sortie standard vers un fichier en Python?

Comment rediriger stdout vers un fichier arbitraire en Python?

Lorsqu'un script Python de longue durée (par exemple, une application Web) est démarré depuis la session ssh et backgounded, et que la session ssh est fermée, l'application déclenche IOError et échoue dès qu'elle tente d'écrire. stdout. Je devais trouver un moyen de faire en sorte que l'application et les modules soient générés dans un fichier plutôt que stdout pour éviter les erreurs dues à IOError. Actuellement, j'utilise Nohup pour rediriger la sortie vers un fichier, ce qui me permet de faire 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 d'émettre toujours en sortie vers le terminal (ou peut-être que la ligne sys.stdout = ... ne s'est pas déclenchée du tout). Je sais que cela devrait fonctionner à partir de scripts plus simples sur lesquels j'ai testé, mais je n'ai pas encore eu le temps de tester sur une application Web.

273
user234932

Si vous souhaitez effectuer la redirection dans le script Python, définissez sys.stdout sur un objet fichier.

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

Une méthode beaucoup plus courante consiste à utiliser la redirection Shell lors de l'exécution (identique sous Windows et Linux):

$ python foo.py > file
354
marcog

Il y a contextlib.redirect_stdout() fonction dans 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`')
_

C'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 la sortie standard 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, vous pouvez utiliser os.dup2():

_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é à la place 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 précédemment imprimée sur stdout passe maintenant à _output.txt_ tant que le gestionnaire de contexte stdout_redirected() est actif.

Remarque: stdout.flush() ne vide pas les mémoires tampons C stdio sur Python 3, où les E/S sont implémentées directement sur read()/write() appels système. Pour vider tous les flux de sortie C stdio ouverts, vous pouvez appeler explicitement libc.fflush(None) si une extension C utilise des E/S basées 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)
_

Remarque: stdout_redirected() mélange les E/S mises en mémoire tampon (_sys.stdout_ généralement) et les entrées/sorties non mises en tampon (opérations sur les descripteurs de fichier directement). Attention, il pourrait y avoir mise en mémoire tamponproblèmes .

Pour répondre, votre modification: vous pouvez utiliser python-daemon pour démoniser votre script et utiliser le module logging (comme @ (erikb85 suggéré ) au lieu de print instructions et redirige simplement stdout pour votre script Python de longue durée que vous exécutez avec Nohup maintenant.

140
jfs

vous pouvez essayer cela 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
88
Yuda Prawira

Les autres réponses ne couvraient pas le cas où vous souhaitez que les processus forkés partagent votre nouvelle sortie standard.

Pour faire ça:

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

Cité de PEP 343 - La déclaration "with" (déclaration d'importation ajoutée):

Rediriger temporairement la sortie standard:

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"

Bien sûr, ce n'est pas fil-sûr, mais ni cette danse ni cette danse ne sont faites manuellement. Dans les programmes à un seul thread (par exemple, dans les scripts), c'est une façon populaire de faire les choses.

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

Sur la base de cette réponse: https://stackoverflow.com/a/5916874/1060344 , voici une autre façon de déterminer celle que j'utilise dans l'un de mes projets. Pour tout ce que vous remplacez par sys.stderr ou sys.stdout, vous devez vous assurer que le remplacement est conforme à l'interface file, en particulier s'il s'agit de quelque chose que vous faites parce que stderr/stdout est utilisé autre bibliothèque qui n'est pas sous votre contrôle. Cette bibliothèque peut utiliser d'autres méthodes d'objet fichier.

Vérifiez cette manière où je laisse toujours tout aller faire stderr/stdout (ou n'importe quel fichier d'ailleurs) et envoie également 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
vaidik

Vous avez besoin d’un multiplexeur de terminal du type tmux ou écran GN

Je suis surpris qu'un petit commentaire de Ryan Amos sur la question initiale soit la seule mention d'une solution de loin préférable à toutes les autres solutions proposées, même si la ruse python est astucieuse et combien upvotes qu'ils ont reçu. Suite au commentaire de Ryan, tmux est une alternative intéressante à l'écran GNU.

Mais le principe est le même: si jamais vous souhaitez laisser un poste vacant pendant que vous vous déconnectez, dirigez-vous vers le café pour prendre un sandwich, sautez dans la salle de bain, rentrez chez vous (etc.), puis plus tard, reconnectez-vous à votre session de terminal depuis n’importe où ou depuis n’importe quel ordinateur comme si vous n’aviez jamais été absent, les multiplexeurs de terminaux sont la réponse . Considérez-les comme un VNC ou un poste de travail distant pour les sessions de terminal. Tout le reste est une solution de contournement. En prime, lorsque le patron et/ou le partenaire arrive et que vous ctrl-w/cmd-w par inadvertance, la fenêtre de votre terminal au lieu de la fenêtre de votre navigateur avec son contenu suspect, vous n'aurez pas perdu les 18 dernières heures de traitement. !

2
duncan

Voici une variante de Yuda Prawira answer:

  • implémenter flush() et tous les attributs de fichier
  • écrivez-le en tant que gestionnaire de contexte
  • capturer stderr également

.

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

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

Un avantage supplémentaire de la ré-exécution de votre programme est que vous pouvez choisir les 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 de l'exécution d'un double fork lors de la création d'un démon?

0
jpaugh

@ marcog

La seconde option n’est valable que si le script est exécuté de manière instantanée. Le script doit être exécuté complètement. La sortie est ensuite placée dans ce fichier. Les boucles infinies ne doivent pas être présentes (de manière optimale). La meilleure solution s'il s'agit d'un simple script.

0
yunus