Перенаправить stdout в файл в Python?

Как перенаправить stdout на произвольный файл в Python?

Когда запущенный Python script (например, веб-приложение) запускается из сеанса ssh и завершается, а сеанс ssh закрывается, приложение будет увеличивать IOError и не сможет выполнить попытку записи в stdout, Мне нужно было найти способ сделать приложение и модули выводятся в файл, а не stdout, чтобы предотвратить сбой из-за IOError. В настоящее время я использую nohup для перенаправления вывода в файл, и это выполняет эту работу, но мне было интересно, есть ли способ сделать это, не используя nohup, из любопытства.

Я уже пробовал sys.stdout = open('somefile', 'w'), но это не мешает некоторым внешним модулям выводить на терминал (или, может быть, строка sys.stdout = ... вообще не срабатывает). Я знаю, что он должен работать с более простыми сценариями, которые я тестировал, но у меня еще не было времени на тестирование в веб-приложении.

Ответы

Ответ 1

Если вы хотите выполнить перенаправление внутри скрипта Python, установите sys.stdout в файловый объект.

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

Гораздо более распространенным методом является использование перенаправления оболочки при выполнении (то же самое в Windows и Linux):

$ python foo.py > file

Ответ 2

В 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

Тот же пример работает теперь, если вместо redirect_stdout() используется stdout_redirected():

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, чтобы демонтировать ваш script и использовать модуль logging (в качестве @erikb85 предложил) вместо операторов print и просто перенаправлял stdout для вашего долгого Python script, который вы используете с помощью nohup.

Ответ 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

Другие ответы не охватывают случай, когда вы хотите, чтобы разветвленные процессы делили ваш новый 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

Ответ 5

Цитируется из 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"

Конечно, это не потокобезопасно, но ни один из них не выполняет этот же танец вручную. В однопоточных программах (например, в скриптах) это популярный способ ведения дел.

Ответ 6

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

Ответ 7

Вам нужен терминальный мультиплексор, например tmux или Экран GNU

Я удивлен, что небольшой комментарий Райана Амоса к первоначальному вопросу - это единственное упоминание о решении, которое гораздо предпочтительнее для всех остальных, независимо от того, насколько умны хитрость python и сколько их стоит, получив. В дополнение к комментарию Ryan, tmux - хорошая альтернатива экрану GNU.

Но принцип тот же: если вы когда-нибудь захотите оставить работу на терминале, когда вы выходите из системы, идите в кафе для бутерброда, поп в ванную, идите домой (и т.д.), а затем, повторно подключитесь к терминальному сеансу из любого места или любого компьютера, как будто вы никогда не были в отъезде, терминальные мультиплексоры являются ответом. Подумайте о них как VNC или удаленный рабочий стол для сеансов терминала. Все остальное - обходной путь. В качестве бонуса, когда приходит босс и/или партнер, и вы случайно создаете ctrl-w/cmd-w в своем оконном окне вместо окна вашего браузера с его изворотливым контентом, вы не потеряете последние 18 часов обработки

Ответ 8

На основе этого ответа: fooobar.com/questions/7962/..., вот еще один способ, который я понял, который я использую в одном из моих проектов. Что бы вы ни заменили 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)

Ответ 9

Вот вариант ответа 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())

Ответ 10

Программы, написанные на других языках (например, C), должны выполнять специальную магию (называемую двойным форсированием), чтобы отделить от терминала (и предотвратить процессы зомби). Поэтому я считаю, что лучшим решением является имитация ими.

A плюс повторного выполнения вашей программы, вы можете выбрать перенаправления в командной строке, например. /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Смотрите это сообщение для получения дополнительной информации: В чем причина выполнения двойной вилки при создании демона?

Ответ 11

@marcog

Второй вариант хорош только в том случае, если сценарий исполняется на ходу. Или сценарий должен выполняться полностью только тогда, когда вывод поступает в этот файл и бесконечные циклы не должны присутствовать (оптимально). Лучшее решение, если это простой скрипт.

Ответ 12

Вот и я наконец понял это. Я сделал следующее:


import os
from contextlib import ExitStack, redirect_stdout, redirect_stderr, suppress
from io import StringIO
err = None
with ExitStack() as stack, suppress(Exception) as s3, suppress(ValueError) as s2, suppress(SystemError) as s1:
    null_stream = StringIO()
    with redirect_stdout(null_stream) as s4:
        try:
            old = sys.tracebacklimit
            sys.tracebacklimit = 0
        except:
            sys.tracebacklimit = 0
            old = 1000
        stack.enter_context(redirect_stdout(null_stream))
        try:
            print('problem code here')
        except Exception as errmsg:
            err = errmsg
        finally:
            stack.close()
            sys.tracebacklimit = 1000
if err:
    raise err

Я отправил все сообщения в StringIO и использую ExitStack() вместе с контекстом подавления, чтобы остановить отображение чего-либо. Это приводит к проблеме, поэтому в попытке/кроме/наконец я сохранил любую реальную ошибку, и если ее нет, выведите эту ошибку.

Это не отличное решение, но оно работает. У меня нет доступа к C-коду, как меня просили.