问题:将stdout重定向到Python中的文件?
如何在Python中将stdout重定向到任意文件?
当从ssh会话中启动运行了很长时间的Python脚本(例如Web应用程序)并进行背景调整,并且ssh会话关闭时,该应用程序将在尝试写入stdout时引发IOError并失败。我需要找到一种方法来使应用程序和模块输出到文件而不是stdout,以防止由于IOError而导致失败。当前,我使用nohup将输出重定向到文件,并且可以完成工作,但是我想知道是否有一种出于好奇而无需使用nohup的方法。
我已经尝试过了sys.stdout = open('somefile', 'w')
,但是这似乎并不能阻止某些外部模块仍然输出到终端(或者sys.stdout = ...
线路根本没有触发)。我知道它应该可以通过我测试过的简单脚本来工作,但是我还没有时间在Web应用程序上进行测试。
回答 0
如果要在Python脚本中进行重定向,则设置sys.stdout
为文件对象可以解决问题:
import sys
sys.stdout = open('file', 'w')
print('test')
一种更常见的方法是在执行时使用外壳重定向(与Windows和Linux相同):
$ python foo.py > file
回答 1
Python 3.4中有contextlib.redirect_stdout()
功能:
from contextlib import redirect_stdout
with open('help.txt', 'w') as f:
with redirect_stdout(f):
print('it now prints to `help.text`')
它类似于:
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版本中使用。后一版本不可重用。如果需要,可以将其制成一个。
它不会在文件描述符级别重定向标准输出,例如:
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'
并且'echo this also is not redirected'
不会重定向到该output.txt
文件。
要在文件描述符级别重定向,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
如果stdout_redirected()
使用代替,则现在可以使用相同的示例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')
output.txt
只要stdout_redirected()
上下文管理器处于活动状态,以前打印在stdout上的输出现在将保留。
注意:stdout.flush()
不会在直接在其上实现I / O的Python 3上刷新C stdio缓冲区read()
/ write()
系统调用。要刷新所有打开的C stdio输出流,libc.fflush(None)
如果某些C扩展使用基于stdio的I / O ,则可以显式调用:
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
您可以使用stdout
参数来重定向其他流,而不仅仅是sys.stdout
合并sys.stderr
和sys.stdout
:
def merged_stderr_stdout(): # $ exec 2>&1
return stdout_redirected(to=sys.stdout, stdout=sys.stderr)
例:
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)
注意:stdout_redirected()
混合使用缓冲的I / O(sys.stdout
通常)和未缓冲的I / O(直接对文件描述符进行操作)。当心,可能会有缓冲 问题。
要回答,请进行编辑:您可以logging
模块(如@ erikb85建议)代替print
语句,而仅将stdout重定向到您nohup
现在运行的长期运行的Python脚本。
回答 2
你可以尝试得更好
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
回答 3
其他答案未涵盖您希望分叉的进程共享新标准输出的情况。
要做到这一点:
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
回答 4
引用自PEP 343-“ with”语句(添加的导入语句):
暂时重定向标准输出:
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
用法如下:
with open(filename, "w") as f:
with stdout_redirected(f):
print "Hello world"
当然,这不是线程安全的,但是也没有手动进行相同的舞蹈。在单线程程序中(例如在脚本中),这是一种流行的处理方式。
回答 5
import sys
sys.stdout = open('stdout.txt', 'w')
回答 6
这是Yuda Prawira答案的一种变化:
- 实现
flush()
和所有文件属性 - 写为上下文管理器
stderr
也捕获
。
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())
回答 7
基于以下答案:https : //stackoverflow.com/a/5916874/1060344,这是我弄清楚在我的一个项目中使用的另一种方法。对于要替换sys.stderr
或替换的内容sys.stdout
,必须确保替换符合file
接口要求,尤其是在执行此操作时,因为在其他不受您控制的库中使用了stderr / stdout。该库可能正在使用文件对象的其他方法。
看看这种方式,我仍然可以让所有事情继续进行stderr / stdout(或与此有关的任何文件),并使用Python的日志记录工具将消息发送到日志文件中(但您实际上可以执行任何操作):
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)
回答 8
令我惊讶的是,Ryan Amos对原始问题的一小段评论是唯一提及的解决方案远胜于所有其他提供的解决方案,无论python技巧有多聪明,他们收到了多少票。除了Ryan的评论,tmux是GNU屏幕的不错选择。
但是原理是一样的:如果您发现自己想在退出时让终端机继续运行,可以去咖啡馆吃三明治,然后去洗手间,回家(等),然后再连接到从任何地方或任何计算机终端会话,就好像你从来没有离开,终端多路复用器的答案。将它们视为用于终端会话的VNC或远程桌面。其他任何方法都可以解决。另外,当老板和/或合伙人进来时,您无意间将ctrl-w / cmd-w终端窗口(而不是带有晦涩内容的浏览器窗口)作为ctrl-w / cmd-w,您将不会失去最后18小时的处理价值!
回答 9
用其他语言(例如C)编写的程序必须做特别的魔术(称为双叉)才能与终端分离(并防止僵尸进程)。因此,我认为最好的解决方案是模拟它们。
重新执行程序的好处是,您可以在命令行上选择重定向,例如 /usr/bin/python mycoolscript.py 2>&1 1>/dev/null
有关更多信息,请参见此帖子:创建守护程序时执行双叉的原因是什么?