将stdout重定向到Python中的文件?

285 python stdout

如何将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)

  • 最好保留一个本地副本`stdout = sys.stdout`,这样你就可以在完成后把它放回去,`sys.stdout = stdout`.这样,如果你从一个使用`print`的函数调用你就不会搞砸它们. (37认同)
  • @mgold或者你可以使用`sys.stdout = sys .__ stdout__`来取回它. (36认同)
  • 它不适用于`from sys import stdout`,可能是因为它创建了一个本地副本.你也可以使用`with`,例如`with open('file','w')作为sys.stdout:functionThatPrints()`.您现在可以使用普通的`print`语句实现`functionThatPrints()`. (5认同)
  • @Jan:`buffering = 0`禁用缓冲(可能会对性能产生负面影响(10-100次)).`buffering = 1`启用行缓冲,以便您可以使用`tail -f`作为面向行的输出. (4认同)
  • 如果您在Windows上注意Windows错误 - [当我使用脚本名称在Windows上运行Python脚本时无法重定向输出](http://stackoverflow.com/questions/3018848/) (3认同)
  • 我们需要关闭文件吗? (2认同)

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.stderrsys.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 .

  • `stdout_redirected`很有帮助.请注意,这在doctests中不起作用,因为特殊的`SpoofOut`处理程序doctest用于替换`sys.stdout`没有`fileno`属性. (3认同)

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)

  • 这将对代码产生影响,该代码假设sys.stdout是一个完整的文件对象,其方法如fileno()(包括python标准库中的代码).我会将__getattr __(self,attr)方法添加到将属性查找推迟到self.terminal的方法.`def __getattr __(self,attr):return getattr(self.terminal,attr)` (7认同)
  • 你必须添加`def flush(self):`方法以及类`Logger`. (4认同)
  • @loretoparisi 但是您创建的方法中实际发生了什么? (3认同)

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)

  • 一个需要替换'w'属性,os.O_WRONLY | os.O_CREATE ...不能将字符串发送到"os"命令! (3认同)
  • 在`close(1)`语句之前插入`sys.stdout.flush()`以确保重定向`'file'.文件获得输出.此外,您可以使用`tempfile.mkstemp()`文件代替`'file'`.并且要小心你没有运行其他线程,可以在`os.close(1)`之后但在打开`'file'`之前窃取os的第一个文件句柄以使用句柄. (3认同)
  • 它的os.O_WRONLY | os.O_CREAT ......那里没有E. (2认同)

小智 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)

当然,这不是线程安全的,但也不是手动进行同样的舞蹈.在单线程程序中(例如在脚本中),它是一种流行的处理方式.

  • +1。注意:它不适用于子进程,例如“os.system('echo not redirected')”。[我的答案显示了如何重定向此类输出](http://stackoverflow.com/a/22434262/4279) (2认同)

Cat*_*lus 11

import sys
sys.stdout = open('stdout.txt', 'w')
Run Code Online (Sandbox Code Playgroud)


dun*_*can 5

您需要一个终端多路复用器,例如tmuxGNU screen

令我惊讶的是,Ryan Amos 对原始问题的一个小评论是唯一提到的解决方案远优于所有其他提供的解决方案,无论 python 技巧有多聪明以及他们收到了多少赞成票。根据 Ryan 的评论,tmux 是 GNU screen 的一个很好的替代品。

但原理是一样的:如果您发现自己想在注销时让终端作业继续运行,请前往咖啡馆吃个三明治,去洗手间,回家(等等),然后重新连接到您的计算机。从任何地方或任何计算机进行终端会话就好像您从未离开过一样,终端多路复用器就是答案。将它们视为终端会话的 VNC 或远程桌面。其他任何方法都是解决方法。作为奖励,当老板和/或合作伙伴进来并且您无意中 ctrl-w / cmd-w 终端窗口而不是浏览器窗口及其可疑内容时,您不会丢失过去 18 小时的处理时间!

  • 虽然对于编辑后出现的问题部分来说这是一个很好的答案;它没有回答标题中的问题(大多数人都是从谷歌来到这里寻找标题的) (4认同)

dam*_*mio 5

这是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 次

最近记录:

6 年,5 月 前