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


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

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

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

10 235

10 ответов:

Если вы хотите сделать перенаправление в скрипте Python, установите sys.stdout для объекта file делает трюк:

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. Последняя версия не является многоразовые. Его можно сделать одним при желании.

он не перенаправляет 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' не перенаправляются .

для перенаправления на уровень дескриптора файла,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, где I / O реализуется непосредственно на read()/

вы можете попробовать это слишком много лучше

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):

перенаправить stdout временно:

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')

на основе этого ответа: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)

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

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

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

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

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

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

вот такой вариант Юда Prawira ответ:

  • реализовать flush() и все атрибуты файла
  • напишите его как contextmanager
  • захват 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())