Redirect stdout to a file in Python?
如何将stdout重定向到python中的任意文件?
当一个长时间运行的python脚本(例如,web应用程序)从ssh会话中启动并返回时,ssh会话关闭,应用程序将在尝试写入stdout时引发ioerror并失败。我需要找到一种方法使应用程序和模块输出到一个文件,而不是stdout,以防止由于ioerror而导致的失败。目前,我使用nohup将输出重定向到一个文件,这就完成了任务,但是出于好奇,我想知道是否有一种方法可以不用nohup来完成输出。
我已经尝试过
如果要在python脚本中进行重定向,请将
1 2 3 | import sys sys.stdout = open('file', 'w') print('test') |
更常见的方法是在执行时使用shell重定向(在Windows和Linux上相同):
1 | $ python foo.py > file |
python 3.4中有
1 2 3 4 5 | from contextlib import redirect_stdout with open('help.txt', 'w') as f: with redirect_stdout(f): print('it now prints to `help.text`') |
类似于:
1 2 3 4 5 6 7 8 9 10 | 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 |
可以在早期的Python版本上使用。后一个版本是不可重用的。如果需要,可以做一个。
它不会在文件描述符级别重定向stdout,例如:
1 2 3 4 5 6 7 8 | 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') |
要在文件描述符级别重定向,可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 |
如果使用
1 2 3 4 5 6 7 8 9 10 | 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 ') os.system('echo this is also redirected') print('this is goes back to stdout') |
只要
注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
您可以使用
1 2 | def merged_stderr_stdout(): # $ exec 2>&1 return stdout_redirected(to=sys.stdout, stdout=sys.stderr) |
例子:
1 2 3 4 5 6 | 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) |
注:
要回答这个问题,您可以使用
你可以试试这个太好了
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
其他的答案不包括希望分叉进程共享新stdout的情况。
这样做:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
引自PEP 343——"with"声明(添加进口声明):
临时重定向stdout:
1 2 3 4 5 6 7 8 9 10 | 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 |
使用方法如下:
1 2 3 | with open(filename,"w") as f: with stdout_redirected(f): print"Hello world" |
当然,这不是线程安全的,但也不是手动执行相同的舞蹈。在单线程程序中(例如在脚本中),这是一种常用的处理方法。
1 2 | import sys sys.stdout = open('stdout.txt', 'w') |
你需要一个终端多路复用器,比如TMUX或GNU屏幕
令我惊讶的是,Ryan Amos对最初问题的一个小小的评论是唯一一个比所有其他人都更可取的解决方案,不管python的诡计有多聪明,他们收到了多少赞成票。除了Ryan的评论,TMUX是GNU屏幕的一个不错的替代品。
但是原理是一样的:如果你想在注销时离开一个终端工作,去咖啡馆吃三明治,跳到浴室,回家(等等),然后从任何地方或任何计算机重新连接到终端会话,就像你从未离开过一样,终端多路复用器就是答案。把它们当作终端会话的VNC或远程桌面。任何其他事情都是解决办法。作为额外的好处,当老板和/或合作伙伴进来时,你不经意地在终端窗口中按ctrl-w/cmd-w,而不是在浏览器窗口中输入不可靠的内容,你就不会损失过去18小时的处理时间!
基于这个答案:https://stackoverflow.com/a/5916874/1060344,这里是我在一个项目中使用的另一种方法。无论您用什么替换
在这里,我仍然让所有的事情都执行stderr/stdout(或任何与此相关的文件),并使用python的日志工具将消息发送到日志文件(但您确实可以对此做任何操作):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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) |
用其他语言(如C)编写的程序必须执行特殊的魔法(称为双分叉),以明确地从终端分离(并防止僵尸进程)。所以,我认为最好的解决方案是模仿它们。
重新执行程序的另一个好处是,您可以在命令行上选择重定向,例如
有关更多信息,请参阅本文:创建守护进程时执行双分叉的原因是什么?
马科格
第二个选项只有当脚本在go中执行时才是好的。或者脚本应该完全执行,然后输出进入该文件,并且不应该出现无限循环(最佳)。最好的解决方案,如果它是一个简单的脚本。
以下是Yuda Prawira答案的变体:
- 实现
flush() 和所有文件属性 - 作为ContextManager编写
- 捕获
stderr 。
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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()) |