簡體   English   中英

將標准輸出重定向到 Python 中的文件?

[英]Redirect stdout to a file in Python?

如何將標准輸出重定向到 Python 中的任意文件?

When a long-running Python script (eg, web application) is started from within the ssh session and backgounded, and the ssh session is closed, the application will raise IOError and fail the moment it tries to write to stdout. 我需要找到一種方法將應用程序和模塊 output 制作成文件而不是標准輸出,以防止由於 IOError 而導致失敗。 目前,我使用 nohup 將 output 重定向到一個文件,這樣就完成了工作,但出於好奇,我想知道是否有辦法不使用 nohup 來做到這一點。

我已經嘗試過sys.stdout = open('somefile', 'w') ,但這似乎並不能阻止某些外部模塊仍然輸出到終端(或者sys.stdout =...行沒有觸發全部)。 我知道它應該可以通過我測試過的更簡單的腳本來工作,但我還沒有時間在 web 應用程序上進行測試。

如果您想在 Python 腳本中進行重定向,請將sys.stdout設置為文件對象即可:

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

一種更常見的方法是在執行時使用 shell 重定向(在 Windows 和 Linux 上相同):

$ python foo.py > file

Python 3.4+ 中有 contextlib.redirect_stdout 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')

只要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(直接操作文件描述符)。 當心,可能存在緩沖問題

要回答,您的編輯:您可以使用python-daemon來守護您的腳本並使用logging模塊(如@erikb85 建議的那樣)而不是print語句,並且僅為您現在使用nohup運行的長時間運行的 Python 腳本重定向標准輸出。

你可以試試這個更好

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

其他答案沒有涵蓋您希望分叉進程共享新標准輸出的情況。

要做到這一點:

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

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

當然,這不是線程安全的,但也不是手動執行相同的舞蹈。 在單線程程序中(例如在腳本中),這是一種流行的做事方式。

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

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

您需要一個終端多路復用器,例如tmuxGNU screen

我很驚訝瑞安·阿莫斯 (Ryan Amos) 對原始問題的一個小評論是唯一提到的解決方案比提供的所有其他解決方案更可取,無論 python 技巧可能多么聰明以及他們收到了多少贊成票。 除了 Ryan 的評論之外,tmux 是 GNU screen 的一個很好的替代品。

但原則是一樣的:如果您發現自己想在注銷時讓終端工作繼續運行,請前往咖啡館吃三明治,去洗手間,回家(等),然后重新連接到您的從任何地方或任何計算機終端會話,就好像你從來沒有離開,終端復用器是答案 將它們視為終端會話的 VNC 或遠程桌面。 其他任何方法都是解決方法。 作為獎勵,當老板和/或合作伙伴進來並且您無意中 ctrl-w / cmd-w 您的終端窗口而不是帶有可疑內容的瀏覽器窗口時,您將不會丟失過去 18 小時的處理時間!

基於這個答案: 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)

用其他語言(例如 C)編寫的程序必須使用特殊的魔法(稱為雙分叉)來明確地與終端分離(並防止僵屍進程)。 所以,我認為最好的解決方案是模仿它們。

重新執行程序的一個/usr/bin/python mycoolscript.py 2>&1 1>/dev/null是,您可以在命令行上選擇重定向,例如/usr/bin/python mycoolscript.py 2>&1 1>/dev/null

有關更多信息,請參閱此帖子: 創建守護進程時執行雙叉的原因是什么?

我知道這個問題得到了回答(使用python abc.py > output.log 2>&1 ),但我還是要說:

編寫程序時,不要寫入標准輸出。 始終使用日志記錄輸出您想要的任何內容。 將來當您想要重定向、過濾、旋轉輸出文件時,這會給您很大的自由。

正如@jfs 所述,大多數解決方案將無法正確處理某些類型的標准輸出 output,例如來自 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")

根據之前對這篇文章的回答,我為自己編寫了這個 class 作為一種更緊湊和更靈活的方式來重定向代碼段 output - 這里只是一個列表 - 並確保 Z78E6221F6393D1356681DB398CED4 之后是正常的。

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\n\n")
    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

還有其他版本使用上下文,但沒有這么簡單。 實際上,我只是用谷歌搜索以仔細檢查它是否可以工作,但很驚訝沒有看到它,因此對於尋找安全且僅針對上下文塊中的代碼的快速解決方案的其他人來說,這里是:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM