stdout을 파이썬의 파일로 리디렉션 하시겠습니까?


314

파이썬에서 stdout을 임의의 파일로 어떻게 리디렉션합니까?

ssh 세션 내에서 오래 실행되는 Python 스크립트 (예 : 웹 응용 프로그램)가 시작되고 백 바운드가 발생하고 ssh 세션이 닫히면 응용 프로그램에서 IOError가 발생하고 stdout에 쓰려고하는 순간 실패합니다. IOError로 인한 오류를 방지하기 위해 응용 프로그램 및 모듈을 stdout이 아닌 파일로 출력하는 방법을 찾아야했습니다. 현재 nohup을 사용하여 출력을 파일로 리디렉션하면 작업이 완료되지만 호기심으로 nohup을 사용하지 않고 수행 할 수있는 방법이 있는지 궁금합니다.

이미 시도 sys.stdout = open('somefile', 'w')했지만 이것이 일부 외부 모듈이 여전히 터미널로 출력되는 것을 막지 않는 것 같습니다 (또는 sys.stdout = ...회선이 전혀 발생하지 않았을 수도 있음 ). 테스트 한 간단한 스크립트에서 작동해야한다는 것을 알고 있지만 아직 웹 응용 프로그램에서 테스트 할 시간이 없었습니다.


8
그것은 실제로 파이썬 일이 아니며, 셸 함수입니다. 그냥 같이 스크립트를 실행script.p > file
Falmarri

나는 현재 nohup을 사용하여 문제를 해결하지만 더 영리한 무언가가있을 것이라고 생각했습니다.

1
@ foxbunny : nohup? 왜 간단하게 someprocess | python script.py? 왜 관련이 nohup있습니까?
S.Lott

3
stdlib print에서 logging모듈 을 적용 하려면 명령문을 다시 작성하십시오 . 그럼 당신은 당신이해야하지 대부분의 경우 생산 코드에서 등 원하는 얼마나 많은 출력 제어가 사방에 출력을 리디렉션 할 수 print있지만,이 log.
erikbwork

2
아마도이 문제에 대한 더 좋은 해결책은 screen 명령으로 bash 세션을 저장하고 다른 실행에서 액세스 할 수있게합니다.
라이언 아모스

답변:


403

Python 스크립트 내에서 리디렉션을 수행하려면 sys.stdout파일 객체로 설정 하면 트릭이 수행됩니다.

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

훨씬 일반적인 방법은 실행할 때 셸 리디렉션을 사용하는 것입니다 (Windows 및 Linux에서 동일).

$ python foo.py > file


7
from sys import stdout로컬 복사본을 생성하기 때문에로 작동하지 않습니다 . 또한 with예를 들어와 함께 사용할 수 있습니다 with open('file', 'w') as sys.stdout: functionThatPrints(). 이제 functionThatPrints()일반 print명령문을 사용하여 구현할 수 있습니다 .
mgold

41
로컬 사본을 보관하는 것이 가장 좋습니다 . stdout = sys.stdout완료되면 다시 복사 할 수 있습니다 sys.stdout = stdout. 그렇게하면 사용하는 함수에서 호출되는 경우 print나사를 조이지 마십시오.
mgold

4
@Jan : buffering=0버퍼링을 비활성화합니다 (성능에 부정적인 영향을 줄 수 있음 (10-100 회)). buffering=1라인 버퍼링을 활성화 tail -f하여 라인 지향 출력에 사용할 수 있습니다 .
jfs

41
@mgold 또는 sys.stdout = sys.__stdout__다시 가져 오는 데 사용할 수 있습니다 .
clemtoy

176

파이썬 3.4 에는 contextlib.redirect_stdout()함수 가 있습니다 :

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

이전 파이썬 버전에서 사용할 수 있습니다. 후자의 버전은 재사용 할 수 없습니다 . 원하는 경우 하나 만들 수 있습니다.

파일 디스크립터 레벨에서 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

다음 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()하지 않습니다 I / O가 직접 구현 파이썬 3에 플러시 C 표준 입출력 버퍼 read()/ write()시스템 호출. 열려있는 모든 C stdio 출력 스트림을 플러시하기 위해 libc.fflush(None)일부 C 확장에서 stdio 기반 I / O를 사용하는 경우 명시 적으로 호출 할 수 있습니다 .

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()버퍼 된 I / O ( sys.stdout보통)와 버퍼되지 않은 I / O (파일 설명자에 대한 직접 작업)를 혼합 합니다. 버퍼링 문제 가있을 수 있습니다 .

대답 : 편집 : python-daemon스크립트를 데몬logging ( demonize )하고 print명령문 대신 모듈을 사용 하여 @ erikb85가 제안한 대로 사용할 수 있으며 nohup지금 사용하는 오래 실행되는 Python 스크립트의 stdout을 리디렉션합니다 .


3
stdout_redirected도움이됩니다. SpoofOutdoctest가 대체하기 위해 사용 하는 특수 핸들러 sys.stdout에는 fileno속성 이 없으므로 doctest에서는 작동하지 않습니다 .
Chris Johnson

@ChrisJohnson : 올리지 않으면 ValueError("Expected a file (`.fileno()`) or a file descriptor")버그입니다. 올리지 않습니까?
jfs

그것은 그 오류를 일으켜서 doctest 내에서 사용할 수 없게 만듭니다. doctest 내에서 함수를 사용하려면 doctest.sys.__stdout__일반적으로 사용할 위치 를 지정해야 합니다 sys.stdout. 이것은 함수에 문제가 아니며, doctest에 필요한 편의를 제공하기 때문에 stdout을 실제 파일이 가진 모든 속성을 갖지 않는 객체로 대체하기 때문입니다.
Chris Johnson

stdout_redirected()stdout매개 변수는, 당신이 그것을 설정할 수 sys.__stdout__있습니다 (유효한해야 원래 파이썬 표준 출력 리디렉션 할 경우 .fileno()대부분의 경우)를. 전류 sys.stdout가 다른 경우 전류에 대해서는 아무 것도 수행하지 않습니다 . 사용하지 마십시오 doctest.sys; 우연히 사용할 수 있습니다.
jfs

이것은 실제로 잘 작동합니다. 즉 stdout과 stderr를 fd로 리디렉션합니다. with stdout_redirected(to=fd): with merged_stderr_stdout(): print('...'); print('...', file=sys.stderr)
neok

91

당신은 이것을 훨씬 더 잘 시도 할 수 있습니다

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

logger또는 에 배관에 대한 제안 사항이 syslog있습니까?
dsummersl

파일을 편집하려면 유용하지 않습니다. 어쨌든 좋은 트릭을 위해 +1
aIKid

10
이는 sys.stdout이 fileno () (python 표준 라이브러리의 코드 포함)와 같은 메소드를 가진 완전한 파일 오브젝트라고 가정하는 코드에 영향을 미칩니다. __getattr __ (self, attr) 메소드를 속성 조회를 self.terminal로 연기하는 메소드에 추가합니다. def __getattr__(self, attr): return getattr(self.terminal, attr)
peabody

4
def flush(self):class에 메소드 를 추가해야합니다 Logger.
loretoparisi

1
@loretoparisi 그러나 실제로 만드는 방법에는 어떤 것이 있습니까?
elkshadow5

28

다른 답변은 갈래 프로세스가 새 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

3
'w'속성을 os.O_WRONLY | os.O_CREATE로 바꿔야합니다. 문자열을 "os"명령으로 보낼 수 없습니다!
Ch'marr

3
경로 재 지정 파일이 출력을 얻 도록 명령문 sys.stdout.flush()앞에 a 를 삽입 close(1)하십시오 'file'. 또한 tempfile.mkstemp()대신에 파일을 사용할 수 있습니다 'file'. 그리고 핸들 을 사용 os.close(1)하기 위해 파일을 'file'열기 전에 및 열기 전에 OS의 첫 번째 파일 핸들을 훔칠 수있는 다른 스레드가 실행되지 않도록주의하십시오 .
Alex Robinson

2
그것의 os.O_WRONLY | os.O_CREAT ... 거기에는 E가 없습니다.
Jeff Sheffield


@ Ch'marr O_CREATE가 아니라 O_CREAT입니다.
quant_dev

28

PEP 343 에서 인용 - "with"명세서 (가져온 명세서) :

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"

이것은 물론 스레드 안전하지는 않지만 수동으로 동일한 춤을 수행하지는 않습니다. 단일 스레드 프로그램 (예 : 스크립트)에서는 널리 사용되는 방법입니다.


1
+1. 참고 : 하위 프로세스 (예 :)에서는 작동하지 않습니다 os.system('echo not redirected'). 내 답변은 이러한 출력을 리디렉션하는 방법을 보여줍니다
jfs

파이썬 3.4부터 거기 redirect_stdoutcontextlib
월터 Tross


3

다음은 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())

2

이 답변 ( https://stackoverflow.com/a/5916874/1060344)을 기반으로 내 프로젝트 중 하나에서 사용하는 다른 방법을 알아 냈습니다. 대체 sys.stderr하거나 대체하는 모든 것에 대해 , 특히 stderr / stdout이 사용자가 제어하지 않는 다른 라이브러리에서 사용되기 때문에 수행중인 작업 인 경우 sys.stdout대체가 file인터페이스를 준수하는지 확인해야합니다 . 해당 라이브러리가 다른 파일 객체 방법을 사용 중일 수 있습니다.

이 방법으로 모든 것이 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)

2

tmux 또는 GNU 화면 과 같은 터미널 멀티플렉서가 필요합니다

Ryan Amos의 원래 질문에 대한 작은 의견은 파이썬의 속임수가 얼마나 영리하고 얼마나 많은 공짜를 받았는지에 상관없이 제안하는 다른 모든 사람들에게 훨씬 더 나은 해결책에 대한 유일한 언급이라는 것에 놀랐습니다. Ryan의 의견 외에도 tmux는 GNU 화면의 훌륭한 대안입니다.

그러나 원칙은 동일합니다. 로그 아웃하는 동안 터미널 작업을 끝내고 싶다면 카페로 가서 샌드위치를 ​​마시고 화장실에 들어가서 집에 가십시오 (등). 다른 곳이나 컴퓨터에서 터미널 세션을 마치지 않아도 터미널 멀티플렉서가 해답입니다. 터미널 세션을위한 VNC 또는 원격 데스크톱이라고 생각하십시오. 다른 것은 해결 방법입니다. 보너스로 보스 및 / 또는 파트너가 들어 와서 실수로 콘텐츠가 포함 된 브라우저 창 대신 터미널 창을 ctrl-w / cmd-w로 변경해도 마지막 18 시간의 처리 손실을 잃지 않습니다. !


4
편집 후에 나타난 질문의 일부에 대한 좋은 대답이지만; 그것은 제목의 질문에 대답하지 않습니다 (대부분의 사람들은 제목을 구글에서 여기 온다)
jfs

0

다른 언어로 작성된 프로그램 (예 : C)은 터미널에서 분리하고 좀비 프로세스를 방지하기 위해 특별한 마술 (더블 포킹)을 수행해야합니다. 그래서 최선의 해결책은 그것들을 모방하는 것입니다.

프로그램을 다시 실행하면 다음과 같이 명령 줄에서 리디렉션을 선택할 수 있습니다. /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

자세한 내용은이 게시물을 참조하십시오 . 데몬을 만들 때 이중 포크를 수행하는 이유는 무엇입니까?


어 .. 나는 자신의 이중 분기를 관리하는 프로세스의 팬이라고 말할 수 없습니다. 관용구가 너무 일반적이며 조심하지 않으면 잘못 코딩하기 쉽습니다. 더 나은 포 그라운드에서 실행 프로세스를 작성하고 시스템 백그라운드 작업 관리자 (사용 systemd, upstart) 또는 다른 유틸리티 ( daemon(1)프로세스 복제 상용구를 처리하기 위해).
Lucretiel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.