Перенаправить стандартный вывод в файл в 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"

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

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

Я удивлен тем, что небольшой комментарий Райана Амоса к первоначальному вопросу - единственное упоминание о решении, гораздо более предпочтительном, чем все остальные, предлагаемые, независимо от того, насколько хитрым может быть обман с питоном и сколько голосов они получили. В дополнение к комментарию Райана, 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

См. Этот пост для получения дополнительной информации: Какова причина выполнения двойной вилки при создании демона?

marcog

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

Есть и другие версии, использующие контекст, но все не так просто. На самом деле я только что погуглил, чтобы дважды проверить, будет ли это работать, и был удивлен, не увидев этого, поэтому для других людей, которые ищут быстрое безопасное решение, направленное только на код внутри блока контекста, вот оно:

      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
Другие вопросы по тегам