Перенаправить стандартный вывод в файл в Python?
Как перенаправить стандартный вывод в произвольный файл в Python?
Когда долгоиграющий скрипт Python (например, веб-приложение) запускается изнутри сеанса ssh и отключается, а сеанс ssh закрывается, приложение вызывает IOError и завершает работу в тот момент, когда оно пытается записать в stdout. Мне нужно было найти способ, чтобы приложение и модули выводили в файл, а не в stdout, чтобы предотвратить сбой из-за IOError. В настоящее время я использую nohup для перенаправления вывода в файл, и это делает работу, но мне было интересно, есть ли способ сделать это без использования nohup, из любопытства.
Я уже пробовала sys.stdout = open('somefile', 'w')
, но это не мешает некоторым внешним модулям по-прежнему выводить на терминал (или, возможно, sys.stdout = ...
линия вообще не стреляла). Я знаю, что он должен работать с более простыми скриптами, на которых я тестировал, но у меня еще не было времени для тестирования веб-приложения.
16 ответов
Если вы хотите сделать перенаправление внутри скрипта Python, установите sys.stdout
к объекту файла делает свое дело:
import sys
sys.stdout = open('file', 'w')
print('test')
Гораздо более распространенным методом является использование перенаправления оболочки при выполнении (то же самое в Windows и Linux):
$ python foo.py > file
Есть contextlib.redirect_stdout()
функция в Python 3.4:
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, теперь идет в output.txt
пока stdout_redirected()
контекстный менеджер активен.
Замечания: stdout.flush()
не сбрасывает буферы C stdio на Python 3, где ввод / вывод реализован непосредственно на read()
/ write()
системные вызовы. Чтобы очистить все открытые потоки вывода C stdio, вы можете вызвать libc.fflush(None)
явно, если какое-то расширение C использует основанный на stdio ввод / вывод:
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()
микширует буферизованный ввод / вывод (sys.stdout
обычно) и небуферизованный ввод / вывод (операции с файловыми дескрипторами напрямую). Осторожно, могут быть проблемы с буферизацией.
Чтобы ответить, ваше редактирование: вы могли бы использовать python-daemon
демонизировать ваш скрипт и использовать logging
модуль (как предложено @erikb85) вместо print
заявления и просто перенаправить стандартный вывод для вашего долгосрочного скрипта Python, который вы запускаете с помощью nohup
сейчас.
Ты можешь попробовать это намного лучше
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
Другие ответы не охватывали случай, когда вы хотите, чтобы разветвленные процессы поделились вашим новым 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
Цитируется из PEP 343. - Оператор "с" (добавлен оператор импорта):
Временно перенаправить стандартный вывод:
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"
Конечно, это не потокобезопасно, но никто не делает этот танец вручную. В однопоточных программах (например, в скриптах) это популярный способ работы.
Вот вариант ответа 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
Я удивлен тем, что небольшой комментарий Райана Амоса к первоначальному вопросу - единственное упоминание о решении, гораздо более предпочтительном, чем все остальные, предлагаемые, независимо от того, насколько хитрым может быть обман с питоном и сколько голосов они получили. В дополнение к комментарию Райана, tmux - хорошая альтернатива экрану GNU.
Но принцип тот же: если вы когда-нибудь захотите оставить работу терминала во время выхода из системы, отправляйтесь в кафе за сэндвичем, зайдите в ванную, пойдите домой (и т. Д.), А затем снова подключитесь к вашему терминальная сессия из любого места или с любого компьютера, как если бы вас никогда не было, терминальные мультиплексоры - это ответ. Думайте о них как о VNC или удаленном рабочем столе для терминальных сессий. Все остальное - обходной путь. В качестве бонуса, когда к вам придет босс и / или партнер и вы случайно нажмете ctrl-w / cmd-w в окне терминала, а не в окне браузера с его хитрым контентом, вы не потеряете обработку за последние 18 часов.!
Основываясь на этом ответе: /questions/2273678/perenapravit-standartnyij-vyivod-v-fajl-v-python/2273700#2273700, вот еще один способ, который я выяснил, который я использую в одном из моих проектов. За то, что вы заменяете 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)
Как упомянуто @jfs, большинство решений не будут должным образом обрабатывать некоторые типы вывода stdout, например, из расширений 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")
Я знаю, что на этот вопрос есть ответ (используя
python abc.py > output.log 2>&1
), но я все же должен сказать:
При написании вашей программы не пишите в stdout. Всегда используйте ведение журнала для вывода того, что хотите. Это даст вам большую свободу в будущем, когда вы захотите перенаправлять, фильтровать, вращать выходные файлы.
Программы, написанные на других языках (например, C), должны совершать особую магию (называемую двойным разветвлением), чтобы отсоединиться от терминала (и предотвратить процессы зомби). Поэтому я думаю, что лучшее решение - подражать им.
Плюс повторного выполнения вашей программы, вы можете выбрать перенаправления в командной строке, например, /usr/bin/python mycoolscript.py 2>&1 1>/dev/null
См. Этот пост для получения дополнительной информации: Какова причина выполнения двойной вилки при создании демона?
Второй вариант хорош только в том случае, если сценарий исполняется на ходу. Или сценарий должен выполняться полностью только тогда, когда вывод поступает в этот файл и бесконечные циклы не должны присутствовать (оптимально). Лучшее решение, если это простой скрипт.
Есть и другие версии, использующие контекст, но все не так просто. На самом деле я только что погуглил, чтобы дважды проверить, будет ли это работать, и был удивлен, не увидев этого, поэтому для других людей, которые ищут быстрое безопасное решение, направленное только на код внутри блока контекста, вот оно:
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
Для интересующихся расширяю вопрос. Мне нужно какое-то время записать файл журнала, затем закрыть его, переименовать и после этого использовать обычный стандартный вывод. Как мне это сделать?
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 не будет существовать.
Основываясь на предыдущих ответах на этот пост, я написал этот класс для себя как более компактный и гибкий способ перенаправления вывода фрагментов кода - здесь только в список - и обеспечить последующую нормализацию вывода.
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