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

时间:2011-01-13 00:51:01

标签: python stdout

如何将stdout重定向到Python中的任意文件?

当从ssh会话中启动长时间运行的Python脚本(例如,Web应用程序)并进行后台处理,并且ssh会话关闭时,应用程序将引发IOError并在尝试写入stdout时失败。我需要找到一种方法来将应用程序和模块输出到文件而不是stdout,以防止由于IOError导致的失败。目前,我使用nohup将输出重定向到一个文件,这就完成了工作,但是我想知道是否有办法在不使用nohup的情况下完成它,出于好奇。

我已经尝试了sys.stdout = open('somefile', 'w'),但这似乎并没有阻止某些外部模块仍然输出到终端(或者sys.stdout = ...行根本没有触发)。我知道它应该使用我测试过的简单脚本,但我还没有时间在Web应用程序上进行测试。

13 个答案:

答案 0 :(得分:343)

如果你想在Python脚本中进行重定向,将sys.stdout设置为文件对象就可以了:

import sys
sys.stdout = open('file', 'w')
print('test')

更常见的方法是在执行时使用shell重定向(在Windows和Linux上相同):

$ python foo.py > file

答案 1 :(得分:136)

Python 3.4中有contextlib.redirect_stdout() function

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版本中使用。后一版本不是reusable。如果需要,可以制作一个。

它不会将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')

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上打印的输出现在转到stdout_redirected()

注意:stdout.flush()不会刷新 Python 3上的C stdio缓冲区,其中I / O直接在read() / write()系统调用上实现。要刷新所有打开的C stdio输出流,如果某个C扩展使用基于stdio的I / O,则可以显式调用libc.fflush(None)

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.stderrsys.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(直接对文件描述符进行操作)。请注意,可能有buffering issues

要回答,您的编辑:您可以使用python-daemon守护您的脚本并使用logging模块(作为@erikb85 suggested)而不是print语句,只需重定向stdout即可您现在使用nohup运行的长期运行的Python脚本。

答案 2 :(得分: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

答案 3 :(得分:29)

其他答案并未涉及您希望分叉进程共享新标准输出的情况。

要做到这一点:

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 :(得分:26)

引自PEP 343 -- The "with" Statement(已添加的进口声明):

暂时重定向标准输出:

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 :(得分:11)

import sys
sys.stdout = open('stdout.txt', 'w')

答案 6 :(得分:3)

您需要像tmuxGNU screen

这样的终端多路复用器

我很惊讶Ryan Amos的一个小评论'对于原始问题,唯一提到的解决方案远远优于所有其他提供的解决方案,无论蟒蛇技巧多么聪明,以及他们收到了多少赞成。除了Ryan的评论之外,tmux是GNU屏幕的一个很好的替代品。

但原则是一样的:如果你发现自己想要在登出时离开终端工作,去咖啡馆吃三明治,去洗手间,回家(等),然后,从任何地方或任何计算机重新连接到您的终端会话,就好像您从未离开过,终端多路复用器 答案。将它们视为用于终端会话的VNC或远程桌面。其他任何东西都是变通方法。作为奖励,当老板和/或合作伙伴进来并且您无意中使用其狡猾的内容而不是您的终端窗口而不是您的浏览器窗口时,您将失去最后18个小时的价值加工!

答案 7 :(得分:2)

基于这个答案:https://stackoverflow.com/a/5916874/1060344,这是我想出的一个我在其中一个项目中使用的方法。对于替换sys.stderrsys.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 :(得分:1)

用其他语言(例如C)编写的程序必须明确地执行特殊魔法(称为双重分叉)以从终端分离(并防止僵尸进程)。所以,我认为最好的解决方案是模仿它们。

重新执行程序的一个优点是,您可以在命令行中选择重定向,例如/usr/bin/python mycoolscript.py 2>&1 1>/dev/null

有关详细信息,请参阅此帖子:What is the reason for performing a double fork when creating a daemon?

答案 9 :(得分:0)

@marcog

第二个选项仅在脚本被执行时才是好选择。或者脚本应仅被完全执行,然后输出才会进入该文件,并且应该不存在无限循环(最佳)。最好的解决方案,如果它是一个简单的脚本。

答案 10 :(得分:0)

我知道这个问题得到了回答(使用 python abc.py > output.log 2>&1 ),但我还是要说:

在编写程序时,不要写入标准输出。始终使用日志记录输出您想要的任何内容。以后当您想要重定向、过滤、旋转输出文件时,这会给您很大的自由。

答案 11 :(得分:0)

正如@jfs 所提到的,大多数解决方案都无法正确处理某些类型的 stdout 输出,例如来自 C 扩展的输出。 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")

答案 12 :(得分:-1)

以下是 Yuda Prawira的变体答案:

  • 实施line = line.Replace("へ。", "C") 和所有文件属性
  • 将其写为contextmanager
  • 捕获File.WriteAllText(filename, line, encoding)

flush()