¿Redirigir la salida estándar a un archivo en Python? [duplicar]

Resuelto asked hace 13 años • 15 respuestas

¿Cómo redirijo la salida estándar a un archivo arbitrario en Python?

Cuando un script Python de ejecución prolongada (por ejemplo, una aplicación web) se inicia desde la sesión ssh y se pone en segundo plano, y la sesión ssh se cierra, la aplicación generará un IOError y fallará en el momento en que intente escribir en la salida estándar. Necesitaba encontrar una manera de hacer que la aplicación y los módulos se enviaran a un archivo en lugar de a una salida estándar para evitar fallas debido a IOError. Actualmente, empleo nohup para redirigir la salida a un archivo, y eso hace el trabajo, pero me preguntaba si había una manera de hacerlo sin usar nohup, por curiosidad.

Ya lo intenté sys.stdout = open('somefile', 'w'), pero esto no parece impedir que algunos módulos externos sigan saliendo al terminal (o tal vez la sys.stdout = ...línea no se disparó en absoluto). Sé que debería funcionar a partir de scripts más simples que he probado, pero tampoco he tenido tiempo todavía de probarlo en una aplicación web.

 avatar Jan 13 '11 07:01
Aceptado

Si desea realizar la redirección dentro del script de Python, configurar sys.stdoutun objeto de archivo es suficiente:

# for python3
import sys
with open('file', 'w') as sys.stdout:
    print('test')

Un método mucho más común es utilizar la redirección de shell al ejecutar (lo mismo en Windows y Linux):

$ python3 foo.py > file
moinudin avatar Jan 13 '2011 00:01 moinudin

Hay una contextlib.redirect_stdout()función en 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`')

Esto es similar a:

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 se puede utilizar en versiones anteriores de Python. La última versión no es reutilizable . Se puede hacer uno si se desea.

No redirige la salida estándar en el nivel de descriptores de archivos, por ejemplo:

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'y 'echo this also is not redirected'no son redirigidos al output.txtarchivo.

Para redirigir a nivel de descriptor de archivo, os.dup2()se podría utilizar:

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

El mismo ejemplo funciona ahora si stdout_redirected()se usa en lugar 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')

La salida que anteriormente se imprimió en stdout ahora se mantendrá output.txtmientras stdout_redirected()el administrador de contexto esté activo.

Nota: stdout.flush()no vacía los buffers stdio de C en Python 3 donde la E/S se implementa directamente en las llamadas al sistema read()/ write(). Para vaciar todos los flujos de salida de C stdio abiertos, puede llamar libc.fflush(None)explícitamente si alguna extensión de C usa E/S basada en 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

Puede usar stdoutel parámetro para redirigir otras transmisiones, no solo sys.stdout, por ejemplo, para fusionar sys.stderry sys.stdout:

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

Ejemplo:

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 almacenadas en búfer ( sys.stdoutnormalmente) y E/S sin búfer (operaciones directamente en descriptores de archivos). Cuidado, podría haber problemas con el almacenamiento en búfer .

Para responder, su edición: podría usar python-daemonpara demonizar su secuencia de comandos y usar loggingel módulo (como sugirió @erikb85 ) en lugar de printdeclaraciones y simplemente redirigir la salida estándar para su secuencia de comandos Python de larga duración que ejecuta nohupahora.

jfs avatar Mar 16 '2014 07:03 jfs

puedes intentar esto mucho mejor

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 avatar May 06 '2011 20:05 Yuda Prawira

Las otras respuestas no cubrieron el caso en el que desea que los procesos bifurcados compartan su nueva salida estándar.

Para hacer eso:

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 avatar Jul 24 '2012 14:07 Yam Marcovic

Citado de PEP 343 - La declaración "con" (declaración de importación agregada):

Redirigir la salida estándar temporalmente:

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

Se utiliza de la siguiente manera:

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

Esto no es seguro para subprocesos, por supuesto, pero tampoco lo es hacer este mismo baile manualmente. En programas de un solo subproceso (por ejemplo, en scripts) es una forma popular de hacer las cosas.

Gerli avatar Feb 05 '2013 12:02 Gerli