如何将stdout重定向到Python中的任意文件?
当从ssh会话中启动长时间运行的Python脚本(例如,Web应用程序)并进行后台处理,并且ssh会话关闭时,应用程序将引发IOError并在尝试写入stdout时失败.我需要找到一种方法来将应用程序和模块输出到文件而不是stdout,以防止由于IOError导致的失败.目前,我使用nohup将输出重定向到一个文件,这就完成了工作,但是我想知道是否有办法在不使用nohup的情况下完成它,出于好奇.
我已经尝试了sys.stdout = open('somefile', 'w'),但这似乎并没有阻止一些外部模块仍然输出到终端(或者sys.stdout = ...线路根本没有发射).我知道它应该使用我测试过的简单脚本,但我还没有时间在Web应用程序上进行测试.
mar*_*cog 367
如果要在Python脚本中进行重定向,设置sys.stdout为文件对象可以解决问题:
import sys
sys.stdout = open('file', 'w')
print('test')
Run Code Online (Sandbox Code Playgroud)
一种更常见的方法是在执行时使用shell重定向(在Windows和Linux上相同):
$ python foo.py > file
Run Code Online (Sandbox Code Playgroud)
jfs*_*jfs 151
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`')
Run Code Online (Sandbox Code Playgroud)
它类似于:
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
Run Code Online (Sandbox Code Playgroud)
可以在早期的Python版本上使用.后一版本不可重复使用.如果需要,它可以制成一个.
它不会将stdout重定向到文件描述符级别,例如:
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')
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
如果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')
Run Code Online (Sandbox Code Playgroud)
之前在stdout上打印的输出现在output.txt只要stdout_redirected()上下文管理器处于活动状态就会进行.
注意: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
Run Code Online (Sandbox Code Playgroud)
您可以使用stdout参数来重定向其他流,而不仅仅是sys.stdout合并sys.stderr和sys.stdout:
def merged_stderr_stdout(): # $ exec 2>&1
return stdout_redirected(to=sys.stdout, stdout=sys.stderr)
Run Code Online (Sandbox Code Playgroud)
例:
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)
Run Code Online (Sandbox Code Playgroud)
注意:stdout_redirected()混合缓冲I/O(sys.stdout通常)和无缓冲I/O(直接对文件描述符进行操作).请注意,可能存在缓冲 问题.
要回答,你的编辑:你可以python-daemon用来守护你的脚本并使用logging模块(如@ erikb85建议的)而不是print语句,只是为你nohup现在使用的长期运行的Python脚本重定向stdout .
Yud*_*ira 88
你可以试试这个太好了
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
Run Code Online (Sandbox Code Playgroud)
Yam*_*vic 28
其他答案没有涵盖您希望分叉进程共享新标准输出的情况.
要做到这一点:
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
Run Code Online (Sandbox Code Playgroud)
小智 27
引自PEP 343 - "with"声明(添加了进口声明):
暂时重定向stdout:
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
Run Code Online (Sandbox Code Playgroud)
使用如下:
with open(filename, "w") as f:
with stdout_redirected(f):
print "Hello world"
Run Code Online (Sandbox Code Playgroud)
当然,这不是线程安全的,但也不是手动进行同样的舞蹈.在单线程程序中(例如在脚本中),它是一种流行的处理方式.
Cat*_*lus 11
import sys
sys.stdout = open('stdout.txt', 'w')
Run Code Online (Sandbox Code Playgroud)
您需要一个终端多路复用器,例如tmux或GNU screen
令我惊讶的是,Ryan Amos 对原始问题的一个小评论是唯一提到的解决方案远优于所有其他提供的解决方案,无论 python 技巧有多聪明以及他们收到了多少赞成票。根据 Ryan 的评论,tmux 是 GNU screen 的一个很好的替代品。
但原理是一样的:如果您发现自己想在注销时让终端作业继续运行,请前往咖啡馆吃个三明治,去洗手间,回家(等等),然后重新连接到您的计算机。从任何地方或任何计算机进行终端会话就好像您从未离开过一样,终端多路复用器就是答案。将它们视为终端会话的 VNC 或远程桌面。其他任何方法都是解决方法。作为奖励,当老板和/或合作伙伴进来并且您无意中 ctrl-w / cmd-w 终端窗口而不是浏览器窗口及其可疑内容时,您不会丢失过去 18 小时的处理时间!
这是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())
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
429824 次 |
| 最近记录: |