Redirecionar stdout para um arquivo em Python?

314

Como redireciono o stdout para um arquivo arbitrário no Python?

Quando um script Python de execução demorada (por exemplo, aplicativo Web) é iniciado a partir da sessão ssh e backgounded, e a sessão ssh é fechada, o aplicativo gera IOError e falha no momento em que tenta gravar no stdout. Eu precisava encontrar uma maneira de fazer com que o aplicativo e os módulos saíssem para um arquivo, em vez de stdout para evitar falhas devido ao IOError. Atualmente, emprego o nohup para redirecionar a saída para um arquivo, e isso faz o trabalho, mas fiquei imaginando se havia uma maneira de fazer isso sem usar o nohup, por curiosidade.

Eu já tentei sys.stdout = open('somefile', 'w'), mas isso não parece impedir a saída de alguns módulos externos para o terminal (ou talvez a sys.stdout = ...linha não tenha disparado). Sei que deve funcionar com scripts mais simples nos quais testei, mas também não tive tempo ainda para testar em um aplicativo da web.

Eric Leschinski
fonte
8
Isso não é realmente uma coisa de python, é uma função de shell. Basta executar o seu script comoscript.p > file
Falmarri
Eu atualmente resolver o problema usando nohup, mas eu pensei que poderia haver algo mais inteligente ...
1
@foxbunny: nohup? Por que simplesmente someprocess | python script.py? Por que envolver nohup?
precisa saber é o seguinte
3
Reescreva as printinstruções para aplicar o loggingmódulo a partir do stdlib. Em seguida, você pode redirecionar a saída para qualquer lugar, ter controle sobre a quantidade que deseja, etc. Na maioria dos casos, o código de produção não deve, printmas log.
precisa saber é o seguinte
2
Talvez uma solução melhor para esse problema seja o comando screen, que salvará sua sessão do bash e permitirá o acesso a partir de diferentes execuções.
Ryan Amos

Respostas:

403

Se você deseja fazer o redirecionamento no script Python, definir sys.stdoutum objeto de arquivo faz o seguinte:

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

Um método muito mais comum é usar o redirecionamento de shell ao executar (o mesmo no Windows e Linux):

$ python foo.py > file
moinudin
fonte
3
Se você estiver no Windows atente para o Windows bug - saída não pode redirecionar quando eu executar script Python no Windows usando o nome apenas do roteiro
Piotr Dobrogost
7
Não funciona from sys import stdout, talvez porque crie uma cópia local. Também você pode usá-lo com with, por exemplo with open('file', 'w') as sys.stdout: functionThatPrints(). Agora você pode implementar functionThatPrints()usando printinstruções normais .
MGold
41
É melhor manter uma cópia local, stdout = sys.stdoutpara que você possa devolvê-la quando terminar sys.stdout = stdout,. Dessa forma, se você está sendo chamado de uma função que usa, printvocê não estraga tudo.
MGold
4
@ Jan: buffering=0desativa o buffer (pode afetar negativamente o desempenho (10 a 100 vezes)). buffering=1ativa o buffer de linha para que você possa usar tail -fpara uma saída orientada a linha.
JFS
41
@mgold ou você pode usá sys.stdout = sys.__stdout__-lo para recuperá-lo.
Clemtoy
176

contextlib.redirect_stdout()função no 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`')

É similar à:

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

que pode ser usado em versões anteriores do Python. A última versão não é reutilizável . Pode ser feito um, se desejado.

Ele não redireciona o stdout no nível dos descritores de arquivo, por exemplo:

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'e 'echo this also is not redirected'não são redirecionados para o output.txtarquivo.

Para redirecionar no nível do descritor de arquivo, os.dup2()pode ser usado:

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

O mesmo exemplo funciona agora se stdout_redirected()for usado em vez de 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')

A saída que foi impressa anteriormente no stdout agora passa para output.txtenquanto o stdout_redirected()gerenciador de contexto estiver ativo.

Nota: stdout.flush()não libera buffers de C stdio no Python 3, onde a E / S é implementada diretamente nas chamadas read()/ write()system. Para liberar todos os fluxos de saída C stdio abertos, você poderia chamar libc.fflush(None)explicitamente se algum ramal C usar E / S baseada em 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

Você pode usar o stdoutparâmetro para redirecionar outros fluxos, não apenas sys.stdoutpara mesclar sys.stderre sys.stdout:

def merged_stderr_stdout():  # $ exec 2>&1
    return stdout_redirected(to=sys.stdout, stdout=sys.stderr)

Exemplo:

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)

Nota: stdout_redirected()combina E / S com buffer ( sys.stdoutgeralmente) e E / S sem buffer (operações diretamente nos descritores de arquivo). Cuidado, pode haver problemas de buffer .

Para responder, sua edição: você pode usar python-daemonpara daemonizar seu script e usar o loggingmódulo (como sugerido pelo @ erikb85 ) em vez de printinstruções e apenas redirecionar o stdout para o seu script Python de longa execução que você executa usando nohupagora.

jfs
fonte
3
stdout_redirectedé útil. Esteja ciente de que isso não funciona em documentos de teste, pois o SpoofOutmanipulador especial que o doctest usa para substituir sys.stdoutnão possui um filenoatributo.
21814 Chris Johnson
@ ChrisJohnson: Se não aumentar ValueError("Expected a file (`.fileno()`) or a file descriptor"), é um bug. Tem certeza de que não aumenta?
jfs
Ele gera esse erro, que é o que o torna inutilizável em um teste de documento. Para usar sua função dentro de um teste de documento, parece necessário especificar doctest.sys.__stdout__onde normalmente usaríamos sys.stdout. Isso não é um problema com sua função, apenas uma acomodação necessária para o doctest, pois substitui o stdout por um objeto que não possui todos os atributos que um arquivo verdadeiro teria.
Chris Johnson
stdout_redirected()possui um stdoutparâmetro, você pode defini-lo como sys.__stdout__se desejar redirecionar o stdout python original (que deve ter um válido .fileno()na maioria dos casos). Não faz nada para a corrente, sys.stdoutse forem diferentes. Não use doctest.sys; está disponível por acidente.
jfs
Isso realmente funciona bem, ou seja, redireciona o stdout e o stderr para um fd: with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok
91

você pode tentar isso muito melhor

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
Yuda Prawira
fonte
Alguma sugestão de canalização para loggerou syslog?
dsummersl
Se você deseja editar um arquivo, isso não é muito útil. Enfim +1 para o truque legal
aIKid
10
Isso terá consequências para o código que assume que sys.stdout é um objeto de arquivo completo com métodos como fileno () (que inclui código na biblioteca padrão do python). Eu adicionaria um método __getattr __ (self, attr) ao que adia a atribuição de pesquisa a self.terminal. def __getattr__(self, attr): return getattr(self.terminal, attr)
Peabody
4
Você precisa adicionar def flush(self):método também à classe Logger.
loretoparisi
1
@loretoparisi mas o que realmente se passa no método que você cria?
precisa saber é o seguinte
28

As outras respostas não cobriram o caso em que você deseja que os processos bifurcados compartilhem seu novo stdout.

Fazer isso:

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
Yam Marcovic
fonte
3
é necessário substituir o atributo 'w' por os.O_WRONLY | os.O_CREATE ... não é possível enviar strings para os comandos "os"!
Ch'marr 26/07/12
3
Insira a sys.stdout.flush()antes da close(1)instrução para garantir que o 'file'arquivo de redirecionamento obtenha a saída. Além disso, você pode usar um tempfile.mkstemp()arquivo no lugar de 'file'. E tenha cuidado para não ter outros threads em execução que possam roubar o primeiro identificador de arquivo do sistema operacional após o os.close(1)mas antes que ele 'file'seja aberto para usá-lo.
Alex Robinson
2
seu os.O_WRONLY | os.O_CREAT ... não há E lá.
Jeff Sheffield
@ Ch'marr É O_CREAT, não O_CREATE.
quant_dev
28

Citado em PEP 343 - A declaração "with" (declaração de importação adicionada):

Redirecionar stdout temporariamente:

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

Utilizado da seguinte forma:

with open(filename, "w") as f:
    with stdout_redirected(f):
        print "Hello world"

Isso não é seguro para threads, é claro, mas também não está fazendo a mesma dança manualmente. Nos programas de thread único (por exemplo, em scripts), é uma maneira popular de fazer as coisas.

Gerli
fonte
1
+1. Nota: não funciona para subprocessos, por exemplo os.system('echo not redirected'),. Minha resposta mostra como redirecionar essa saída
jfs 16/03
a partir de Python 3.4 existe redirect_stdoutemcontextlib
Walter Tross 30/04
12
import sys
sys.stdout = open('stdout.txt', 'w')
Cat Plus Plus
fonte
3

Aqui está uma variação da resposta de Yuda Prawira :

  • implementar flush()e todos os atributos de arquivo
  • escreva-o como um gerenciador de contexto
  • capturar stderrtambém

.

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())
Damio
fonte
2

Com base nesta resposta: https://stackoverflow.com/a/5916874/1060344 , eis uma outra maneira de descobrir qual uso em um dos meus projetos. Para o que você substituir sys.stderrou substituir sys.stdout, você deve garantir que a substituição esteja em conformidade com a fileinterface, especialmente se isso for algo que você está fazendo, porque stderr / stdout é usado em alguma outra biblioteca que não está sob seu controle. Essa biblioteca pode estar usando outros métodos de objeto de arquivo.

Confira desta maneira, onde eu ainda deixo tudo funcionar stderr / stdout (ou qualquer outro arquivo) e também envie a mensagem para um arquivo de log usando o recurso de log do Python (mas você pode realmente fazer qualquer coisa com isso):

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)
vaidik
fonte
2

Você precisa de um multiplexador de terminal como o tmux ou a tela GNU

Estou surpreso que um pequeno comentário de Ryan Amos à pergunta original seja a única menção de uma solução preferível a todas as outras em oferta, não importa o quão esperto o truque do python possa ser e quantas votações recebidas. Além do comentário de Ryan, o tmux é uma boa alternativa à tela do GNU.

Mas o princípio é o mesmo: se você quiser deixar um emprego terminal enquanto estiver desconectado, vá ao café para um sanduíche, vá ao banheiro, vá para casa (etc) e depois reconecte-se ao seu sessão de terminal de qualquer lugar ou computador como se você nunca estivesse fora, os multiplexadores de terminal são a resposta. Pense neles como VNC ou desktop remoto para sessões de terminal. Qualquer outra coisa é uma solução alternativa. Como bônus, quando o chefe e / ou o parceiro entram e você inadvertidamente pressiona sua janela do terminal em vez da janela do navegador com seu conteúdo desonesto, você não perde as últimas 18 horas de processamento !

duncan
fonte
4
embora seja uma boa resposta para a parte da pergunta exibida após a edição; não responder a pergunta no título (a maioria das pessoas vêm aqui de google para o título)
jfs
0

Programas escritos em outros idiomas (por exemplo, C) precisam fazer mágica especial (chamada de bifurcação dupla) expressamente para se desconectar do terminal (e para evitar processos zumbis). Então, acho que a melhor solução é imitá-los.

Uma vantagem de reexecutar seu programa é que você pode escolher redirecionamentos na linha de comando, por exemplo /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

Consulte esta publicação para obter mais informações: Qual é o motivo da execução de um fork duplo ao criar um daemon?

jpaugh
fonte
Eh ... não posso dizer que sou fã de processos que gerenciam seus próprios bifurcações. É um idioma tão comum e fácil de codificar errado, se você não for cuidadoso. Melhor escrever seu processo para ser executado em primeiro plano e usar um gerenciador de tarefas em segundo plano do sistema ( systemd, upstart) ou outro utilitário ( daemon(1)) para lidar com o padrão da bifurcação.
Lucretiel