[英]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.stderr
和sys.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())
您需要一個終端多路復用器,例如tmux或GNU screen
我很驚訝瑞安·阿莫斯 (Ryan Amos) 對原始問題的一個小評論是唯一提到的解決方案比提供的所有其他解決方案更可取,無論 python 技巧可能多么聰明以及他們收到了多少贊成票。 除了 Ryan 的評論之外,tmux 是 GNU screen 的一個很好的替代品。
但原則是一樣的:如果您發現自己想在注銷時讓終端工作繼續運行,請前往咖啡館吃三明治,去洗手間,回家(等),然后重新連接到您的從任何地方或任何計算機終端會話,就好像你從來沒有離開,終端復用器是答案。 將它們視為終端會話的 VNC 或遠程桌面。 其他任何方法都是解決方法。 作為獎勵,當老板和/或合作伙伴進來並且您無意中 ctrl-w / cmd-w 您的終端窗口而不是帶有可疑內容的瀏覽器窗口時,您將不會丟失過去 18 小時的處理時間!
基於這個答案: 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)
用其他語言(例如 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.