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.
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
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.
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
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
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.
import sys
sys.stdout = open('stdout.txt', 'w')
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)
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. !
Voici une variante de Yuda Prawira answer:
flush()
et tous les attributs de fichierstderr
é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())
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?
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.