将标准输出重定向到 Python 中的文件?[重复]

2024-11-21 08:33:00
admin
原创
4
摘要:问题描述:如何在 Python 中将 stdout 重定向到任意文件?当长时间运行的 Python 脚本(例如,Web 应用程序)从 ssh 会话中启动并返回,并且 ssh 会话关闭时,应用程序将引发 IOError 并在尝试写入 stdout 时失败。我需要找到一种方法让应用程序和模块输出到文件而不是 st...

问题描述:

如何在 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.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:

你可以尝试一下更好的

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

解决方案 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文件将不存在。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用