web-dev-qa-db-ja.com

標準出力をPythonのファイルにリダイレクトしますか?

Pythonで標準出力を任意のファイルにリダイレクトするにはどうすればいいですか?

Sshセッション内から長時間実行されているPythonスクリプト(Webアプリケーションなど)が起動されてバックグラウンド処理され、sshセッションが閉じられると、アプリケーションはIOErrorを送出し、標準出力に書き込もうとした瞬間に失敗します。 IOErrorによる失敗を防ぐために、標準出力ではなくアプリケーションとモジュールをファイルに出力する方法を見つける必要がありました。現在、出力をファイルにリダイレクトするためにNohupを使用していますが、これで作業は完了しましたが、好奇心から、Nohupを使用せずにそれを実行する方法があるのか​​どうかと思いました。

私はすでにsys.stdout = open('somefile', 'w')を試してみましたが、これはいくつかの外部モジュールがまだ端末に出力するのを妨げるようには思われません(あるいはおそらくsys.stdout = ...行はまったく起動しませんでした)。私がテストしたより単純なスクリプトでもうまくいくはずですが、Webアプリケーションでテストする時間がまだありません。

265
hayavuk

Pythonスクリプト内でリダイレクトを行いたい場合は、sys.stdoutをファイルオブジェクトに設定してください。

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

もっと一般的な方法は、実行時にシェルリダイレクションを使用することです(WindowsでもLinuxでも同じです)。

$ python foo.py > file
338
marcog

Python 3.4には contextlib.redirect_stdout() function があります。

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_redirected()コンテキストマネージャがアクティブである限りoutput.txtに送られるようになりました。

注:I/Oがstdout.flush()/read()システムコールに直接実装されているPython 3では、write()はC標準バッファをフラッシュしません。開いているCのstdio出力ストリームをすべてフラッシュするには、Cの拡張機能がstdioベースのI/Oを使用している場合は、明示的にlibc.fflush(None)を呼び出すことができます。

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

sys.stdoutsys.stderrをマージするなど、sys.stdoutだけでなく他のストリームをリダイレクトするためにstdoutname__パラメータを使用することができます。

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 を使い、loggingname__ステートメントの代わりにprintname__モジュール(as @ erikb85推奨 )を使用今Nohupname__を使用しています。

136
jfs

あなたはこれをもっと上手く試すことができます

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
88
Yuda Prawira

他の答えはあなたが分岐したプロセスにあなたの新しい標準出力を共有して欲しいケースをカバーしませんでした。

それをするには:

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
29
Yam Marcovic

PEP 343からの引用 - "with"ステートメント (追加されたインポートステートメント):

標準出力を一時的にリダイレクトします。

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"

もちろんこれはスレッドセーフではありませんが、同じダンスを手動で行うこともありません。シングルスレッドプログラム(スクリプトなど)では、これが一般的な方法です。

26
Gerli
import sys
sys.stdout = open('stdout.txt', 'w')
11
Cat Plus Plus

tmux または GNU screen のようなターミナルマルチプレクサが必要です。

元の質問に対するRyan Amosの小さなコメントが、他のすべての人よりもはるかに好ましい解決策について言及しているにすぎないことに驚いています。 Ryanのコメントに加えて、tmuxはGNU screenの代わりになります。

しかし、原則は同じです。ログアウトしている間にターミナルジョブを実行したままにしたい場合は、カフェでサンドイッチを食べ、バスルームを開き、帰宅し(など)、その後、もう一度接続しますどこにいても、どんなコンピュータからでもターミナルセッションを開くことができますが、ターミナルマルチプレクサはthe answerです。それらをターミナルセッション用のVNCまたはリモートデスクトップと考えてください。それ以外は回避策です。おまけとして、上司やパートナーがやってきて、不用意にブラウザウィンドウではなくターミナルウィンドウを誤ってctrl-w/cmd-w操作しても、過去18時間分の処理が失われることはありません。 !

3
duncan

この答えに基づいて: https://stackoverflow.com/a/5916874/1060344 、これが私が私のプロジェクトの1つで使用することを考え出した別の方法です。 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)
2
vaidik

他の言語(例えばC)で書かれたプログラムは、端末からデタッチする(そしてゾンビプロセスを防ぐ)ために(ダブルフォークと呼ばれる)特別な魔法をかけなければなりません。だから、私はそれらをエミュレートすることが最善の解決策だと思います。

あなたのプログラムを再実行することのプラスは、あなたがコマンドラインでリダイレクトを選ぶことができるということです。 /usr/bin/python mycoolscript.py 2>&1 1>/dev/null

詳細については、この記事を参照してください: デーモンを作成するときにダブルフォークを実行する理由は何ですか?

1
jpaugh

Yuda Prawira answerのバリエーションは次のとおりです。

  • 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())
1
damio

@marcog

2番目のオプションは、スクリプトが実行中に実行された場合にのみ有効です。スクリプトが完全に実行された後に、出力がそのファイルに送信されます。それが単純なスクリプトであれば最善の解決策。

0
yunus