首页 文章

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

提问于
浏览
250

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

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

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

11 回答

  • 130

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

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

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

    $ python foo.py > file
    
  • 2

    引自PEP 343 -- The "with" Statement(添加了import语句):

    暂时重定向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
    

    使用如下:

    with open(filename, "w") as f:
        with stdout_redirected(f):
            print "Hello world"
    

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

  • 1

    基于这个答案: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)
    
  • -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?

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

    @marcog

    第二个选项只有当脚本在go中被执行时才有用 . 或者脚本应该完全执行然后输出进入该文件并且无限循环应该存在(最佳) . 最佳解决方案,如果它是一个简单的脚本

  • 83

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

    只要 stdout_redirected() 上下文管理器处于活动状态,之前在stdout上打印的输出现在变为 output.txt .

    注意: 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 语句,只是为您现在使用 nohup 运行的长期运行的Python脚本重定向stdout .

  • 2

    其他答案并未涵盖您希望分叉进程共享新stdout的情况 .

    要做到这一点:

    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
    
  • 23

    你需要像tmuxGNU screen这样的终端多路复用器

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

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

  • 0

    以下是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())
    
  • 311

    你可以试试这个太好了

    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
    

相关问题