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


313

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

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

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


8
那不是真正的python东西,它是一个shell函数。就像运行脚本script.p > file
Falmarri

我目前使用nohup解决了问题,但我认为可能有些更聪明的

1
@foxbunny:不行吗?为什么简单someprocess | python script.py?为什么要参与nohup
S.Lott

3
重写print语句以logging从stdlib 应用模块。然后,你可以随处重定向输出,比你有多大的输出要等。在大多数情况下,生产代码不应该控制print,但log
erikbwork 2014年

2
screen命令可能是解决此问题的更好解决方案,它将保存您的bash会话并允许您从不同的运行中访问它。
瑞安·阿莫斯

Answers:


402

如果要在Python脚本中进行重定向,则设置sys.stdout为文件对象可以解决问题:

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

一种更常见的方法是在执行时使用外壳重定向(与Windows和Linux相同):

$ python foo.py > file

3
如果您使用的是Windows,请
当心

7
它不适用于from sys import stdout,可能是因为它创建了本地副本。您也可以与使用它with,例如with open('file', 'w') as sys.stdout: functionThatPrints()。现在,您可以functionThatPrints()使用普通print语句来实现。
mgold 2012年

41
最好保留本地副本,stdout = sys.stdout以便在完成后可以将其放回sys.stdout = stdout。这样,如果您从使用某个函数的函数中被调用,print则不会搞砸它们。
mgold 2012年

4
@Jan:buffering=0禁用缓冲(这可能会对性能产生负面影响(10到100次))。buffering=1启用行缓冲,以便可以tail -f用于面向行的输出。
jfs 2014年

41
@mgold或您可以sys.stdout = sys.__stdout__用来找回它。
clemtoy 2015年

175

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.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(直接对文件描述符进行操作)。当心,可能会有缓冲 问题

要回答,请进行编辑:您可以python-daemon用来守护脚本并使用logging模块(如@ erikb85建议)代替print语句,而仅将stdout重定向到您nohup现在运行的长期运行的Python脚本。


3
stdout_redirected是有帮助的。请注意,这在SpoofOutdoctest中不起作用,因为doctest用于替换的特殊处理程序sys.stdout没有fileno属性。
克里斯·约翰逊

@ChrisJohnson:如果没有提高,ValueError("Expected a file (`.fileno()`) or a file descriptor")那就是一个错误。您确定它不会升高吗?
jfs 2014年

它确实会引发该错误,这就是使其在doctest中不可用的原因。要在doctest中使用您的函数,似乎有必要指定doctest.sys.__stdout__我们通常使用的位置sys.stdout。这不是您的函数的问题,只是doctest所需的一种容纳方式,因为它用不具有真正文件所具有的所有属性的对象替换了stdout。
克里斯·约翰逊

stdout_redirected()具有stdout参数,sys.__stdout__如果要重定向原始的python stdout(.fileno()在大多数情况下应具有有效值),则可以将其设置为。sys.stdout如果它们不同,则对当前不执行任何操作。不要用doctest.sys; 它是偶然可用的。
jfs 2014年

这确实很好用,即将stdout和stderr重定向到fd: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok

90

你可以尝试得更好

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

对管道输送到logger或有任何建议syslog吗?
dsummersl

如果要编辑文件,此功能不是很有用。无论如何+1是个不错的戏法
akid 2014年

10
这将对假定sys.stdout是具有诸如fileno()之类的完整文件对象的代码产生影响(包括python标准库中的代码)。我将添加__getattr __(self,attr)方法到该方法中,该方法将属性查找推迟到self.terminal。 def __getattr__(self, attr): return getattr(self.terminal, attr)
豌豆

4
您还必须def flush(self):向class 添加方法Logger
loretoparisi

1
@loretoparisi,但是您创建的方法实际上是什么?
elkshadow5

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

3
需要用os.O_WRONLY | os.O_CREATE替换'w'属性...无法将字符串发送到“ os”命令中!
Ch'marr 2012年

3
sys.stdout.flush()close(1)语句之前插入一个,以确保重定向'file'文件获得输出。另外,您可以使用tempfile.mkstemp()文件代替'file'。请注意,没有其他正在运行的线程可以在s之后os.close(1)'file'要使用os的情况下窃取os的第一个文件句柄。
亚历克斯·罗宾逊

2
其os.O_WRONLY | os.O_CREAT ...那里没有E。
杰夫·谢菲尔德


@ Ch'marr是O_CREAT,不是O_CREATE。
–quant_dev

28

引用自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"

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


1
+1。注意:它不适用于子流程,例如os.system('echo not redirected')我的答案显示了如何重定向此类输出
JFS

在Python 3.4开始出现redirect_stdoutcontextlib
瓦尔特Tross


3

这是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())

2

基于以下答案: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)

2

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

令我惊讶的是,Ryan Amos对原始问题的一小段评论是唯一提及的解决方案远胜于所有其他提供的解决方案,无论python技巧有多聪明,他们收到了多少票。除了Ryan的评论,tmux是GNU屏幕的不错选择。

但是原理是一样的:如果您发现自己想在退出时让终端机继续运行,可以去咖啡馆吃三明治,然后去洗手间,回家(等),然后再连接到从任何地方或任何计算机终端会话,就好像你从来没有离开,终端多路复用器答案。将它们视为用于终端会话的VNC或远程桌面。其他任何方法都可以解决。另外,当老板和/或合伙人进来时,您无意间将ctrl-w / cmd-w终端窗口(而不是带有晦涩内容的浏览器窗口)作为ctrl-w / cmd-w,您将不会失去最后18小时的处理价值!


4
对于在编辑后出现的部分问题,这是一个很好的答案;它没有回答标题中的问题(大多数人都是从Google来找标题的)
jfs 2015年

0

用其他语言(例如C)编写的程序必须做特别的魔术(称为双叉)才能与终端分离(并防止僵尸进程)。因此,我认为最好的解决方案是模拟它们。

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

有关更多信息,请参见此帖子:创建守护程序时执行双叉的原因是什么?


嗯...不能说我是流程管理自己的双重分支的粉丝。这是很常见的习惯用法,如果不小心,很容易编写错误代码。更好地写出你的过程,在前台运行,并使用系统后台任务管理器(systemdupstart)或其他实用程序(daemon(1))来处理分叉样板。
Lucretiel 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.