将标准输出重定向到 Python 中的文件?[重复]
- 2024-11-21 08:33:00
- admin 原创
- 35
问题描述:
如何在 Python 中将 stdout 重定向到任意文件?
当长时间运行的 Python 脚本(例如,Web 应用程序)从 ssh 会话中启动并返回,并且 ssh 会话关闭时,应用程序将引发 IOError 并在尝试写入 stdout 时失败。我需要找到一种方法让应用程序和模块输出到文件而不是 stdout,以防止由于 IOError 而失败。目前,我使用 nohup 将输出重定向到文件,这可以完成工作,但出于好奇,我想知道是否有一种方法可以在不使用 nohup 的情况下做到这一点。
我已经尝试过了sys.stdout = open('somefile', 'w')
,但这似乎无法阻止一些外部模块仍然输出到终端(或者可能根本sys.stdout = ...
没有触发该行)。我知道它应该可以在我测试过的更简单的脚本上运行,但我还没有时间在 Web 应用程序上进行测试。
解决方案 1:
如果您想在 Python 脚本中执行重定向,则设置sys.stdout
文件对象即可:
# for python3
import sys
with open('file', 'w') as sys.stdout:
print('test')
一种更常见的方法是在执行时使用 shell 重定向(Windows 和 Linux 上相同):
$ python3 foo.py > file
解决方案 2:
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
')
os.system('echo this is also redirected')
print('this is goes back to stdout')
output.txt
只要stdout_redirected()
上下文管理器处于活动状态,以前在 stdout 上打印的输出就会转到。
注意:stdout.flush()
在 Python 3 上不会刷新 C stdio 缓冲区,因为 I/O 直接在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(直接对文件描述符进行操作)。请注意,可能会出现缓冲 问题。
回答一下,您的编辑:您可以使用python-daemon
守护进程来执行您的脚本并使用logging
模块(如@erikb85建议的那样)而不是print
语句,而只是将 stdout 重定向到您nohup
现在运行的长期运行的 Python 脚本。
解决方案 3:
你可以尝试一下更好的
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
解决方案 4:
其他答案没有涉及您希望分叉进程共享新标准输出的情况。
要做到这一点:
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
解决方案 5:
引自PEP 343--“with”语句(添加了 import 语句):
临时重定向标准输出:
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"
当然,这不是线程安全的,但手动执行同样的操作也不安全。在单线程程序中(例如在脚本中),这是一种流行的做事方式。
解决方案 6:
import sys
sys.stdout = open('stdout.txt', 'w')
解决方案 7:
以下是Yuda Prawira回答的变体:
实现
flush()
和所有文件属性将其写为 contextmanager
捕获
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())
解决方案 8:
你需要一个终端多路复用器,例如tmux或GNU screen
令我惊讶的是,Ryan Amos 对原始问题的一小段评论是唯一提到的解决方案,远胜于所有其他解决方案,无论 Python 技巧多么巧妙,也无论他们获得了多少赞。Ryan 的评论进一步指出,tmux 是 GNU screen 的一个不错的替代品。
但原理是一样的:如果您发现自己想在注销时让终端作业继续运行,去咖啡馆吃三明治,去洗手间,回家(等等),然后稍后从任何地方或任何计算机重新连接到您的终端会话,就好像您从未离开过一样,那么终端多路复用器就是答案。将它们视为终端会话的 VNC 或远程桌面。其他任何东西都是解决方法。作为奖励,当老板和/或合作伙伴进来时,您无意中按 ctrl-w / cmd-w 您的终端窗口而不是带有可疑内容的浏览器窗口,您不会丢失过去 18 小时的处理!
解决方案 9:
根据这个答案: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)
解决方案 10:
用其他语言(例如 C)编写的程序必须执行特殊的魔法(称为双重分叉)才能明确地脱离终端(并防止僵尸进程)。所以,我认为最好的解决方案是模拟它们。
重新执行程序的一个好处是,你可以在命令行上选择重定向,例如/usr/bin/python mycoolscript.py 2>&1 1>/dev/null
有关详细信息,请参阅此帖子:创建守护进程时执行双分叉的原因是什么?
解决方案 11:
我知道这个问题已经得到解答了(使用python abc.py > output.log 2>&1
),但我还是要说:
编写程序时,不要写入 stdout。始终使用日志记录来输出您想要的任何内容。当您想要重定向、过滤、旋转输出文件时,这将为您提供很大的自由。
解决方案 12:
正如 @jfs 所提到的,大多数解决方案都无法正确处理某些类型的 stdout 输出,例如来自 C 扩展的 stdout 输出。PyPI 上有一个名为 的模块可以处理所有这些问题wurlitzer
。您只需要它的sys_pipes
上下文管理器。使用起来很简单:
from contextlib import redirect_stdout
import os
from wurlitzer import sys_pipes
log = open("test.log", "a")
with redirect_stdout(log), sys_pipes():
print("print statement")
os.system("echo echo call")
解决方案 13:
根据这篇文章之前的回答,我为自己编写了这个类,作为一种更紧凑、更灵活的方式来重定向代码片段的输出 - 这里只是一个列表 - 并确保之后的输出被规范化。
class out_to_lt():
def __init__(self, lt):
if type(lt) == list:
self.lt = lt
else:
raise Exception("Need to pass a list")
def __enter__(self):
import sys
self._sys = sys
self._stdout = sys.stdout
sys.stdout = self
return self
def write(self,txt):
self.lt.append(txt)
def __exit__(self, type, value, traceback):
self._sys.stdout = self._stdout
用途:
lt = []
with out_to_lt(lt) as o:
print("Test 123
")
print(help(str))
正在更新。我刚发现一个场景,我必须添加两个额外的方法,但很容易适应:
class out_to_lt():
...
def isatty(self):
return True #True: You're running in a real terminal, False:You're being piped, redirected, cron
def flush(self):
pass
解决方案 14:
还有其他版本使用上下文,但没有这么简单。实际上,我只是在谷歌上仔细检查它是否有效,并惊讶地发现没有看到它,因此对于其他寻找安全且仅针对上下文块内代码的快速解决方案的人来说,这里是:
import sys
with open('test_file', 'w') as sys.stdout:
print('Testing 1 2 3')
测试如下:
$ cat redirect_stdout.py import sys with open('test_file', 'w') as sys.stdout: print('Testing 1 2 3') $ python redirect_stdout.py $ cat test_file Testing 1 2 3
解决方案 15:
对于那些感兴趣的人,我扩展了这个问题。我需要在日志文件中写入一段时间,然后关闭它,重命名它,然后使用正常的标准输出。我该怎么做?
print("Start program")
import os
import sys
sys.stdout.flush()
sys.stdout=open("xxxtmp", "wt")
print("xxx")
sys.stdout.close()
sys.stdout = sys.__stdout__
os.rename("xxxtmp", "xxx")
print("End program")
在标准输出上会有:
Start program
End program
在 xxx 将会有:
xxx
如果程序异常退出,xxx文件将不存在。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件