stdoutをPythonのファイルにリダイレクトしますか?


314

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

長時間実行するPythonスクリプト(Webアプリケーションなど)がsshセッション内から開始されバックグラウンドで実行され、sshセッションが閉じられると、アプリケーションはIOErrorを発生させ、stdoutに書き込もうとしたときに失敗します。IOErrorによる失敗を防ぐために、アプリケーションとモジュールをstdoutではなくファイルに出力する方法を見つける必要がありました。現在、私はnohupを使用して出力をファイルにリダイレクトし、それで仕事が完了しましたが、好奇心から、nohupを使用せずにそれを行う方法があるかどうか疑問に思っていました。

私はすでに試しましたsys.stdout = open('somefile', 'w')が、これは一部の外部モジュールがまだターミナルに出力するのを妨げているようではありません(またはsys.stdout = ...ラインがまったく起動しませんでした)。私がテストした簡単なスクリプトで動作するはずですが、まだWebアプリケーションでテストする時間もありませんでした。


8
これは実際にはpythonではなく、シェル関数です。次のようにスクリプトを実行しますscript.p > file
Falmarri

私は現在nohupを使用して問題を解決していますが、もっと賢明なものがあるかもしれないと思いました...

1
@foxbunny:nohup?なぜ単にsomeprocess | python script.py?なぜ関与するのnohupか?
S.Lott、2013年

3
printステートメントを書き直してlogging、stdlibからモジュールを適用します。その後、あなたは、どこにでも出力をリダイレクトするほとんどの場合、生産コードを使用すると、などをしたいどのくらいの出力を制御する必要はありませんすることができprintますがlog
erikbwork 2014年

2
おそらく、この問題のより良い解決策は、bashセッションを保存し、別の実行からアクセスできるようにするscreenコマンドです。
Ryan Amos 2014

回答:


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 14

41
@mgoldまたはsys.stdout = sys.__stdout__それを元に戻すために使用できます。
クレムトイ2015

176

Python 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

以前のバージョンの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'にリダイレクトされていない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.txtstdout_redirected()コンテキストマネージャーがアクティブである限り有効になります。

注:stdout.flush()I / Oがread()/ write()システムコールに直接実装されているPython 3では、C stdioバッファーをフラッシュしません。開いているすべての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モジュールを使用して(@ erikb85推奨)、現在print使用している長期実行Pythonスクリプトのstdoutをリダイレクトするだけですnohup


3
stdout_redirected役立ちます。doctest SpoofOutが置換に使用する特別なハンドラーにsys.stdoutfileno属性がないため、これはdoctest内では機能しないことに注意してください。
クリスジョンソン

@ChrisJohnson:発生しない場合ValueError("Expected a file (`.fileno()`) or a file descriptor")はバグです。それはそれを上げないことを確認しますか?
jfs 14

それはそのエラーを引き起こします、それはそれをdoctest内で使用不可能にするものです。doctest内で関数を使用するdoctest.sys.__stdout__には、通常使用する場所を指定する必要があるようsys.stdoutです。これは関数の問題ではなく、stdoutを実際のファイルが持つすべての属性を持たないオブジェクトに置き換えるため、doctestに必要な調整です。
Chris Johnson

stdout_redirected()持っているstdoutパラメータは、あなたがそれを設定することもできsys.__stdout__ます(有効な持っている必要があり、元のPythonの標準出力リダイレクトしたい場合は.fileno()、ほとんどのケースでは)。sys.stdoutそれらが異なる場合、それは現在に対して何もしません。使用しないでくださいdoctest.sys。偶然入手可能です。
jfs 14

これは本当にうまくいきます、すなわち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 2013年

ファイルを編集したい場合、これはあまり役に立ちません。とにかくいいトリックの+1
aIKid 14年

10
これは、sys.stdoutがfileno()(Python標準ライブラリのコードを含む)などのメソッドを持つ完全なファイルオブジェクトであると想定するコードに影響を与えます。__getattr __(self、attr)メソッドを追加して、self.terminalへの属性ルックアップを延期します。 def __getattr__(self, attr): return getattr(self.terminal, attr)
ピーボディ14

4
def flush(self):クラスにもメソッドを追加する必要がありLoggerます。
loretoparisi 2017

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 2012

3
ステートメントのsys.stdout.flush()前にを挿入してclose(1)、リダイレクト'file'ファイルが出力を取得することを確認します。また、のtempfile.mkstemp()代わりにファイルを使用することもできます'file'。そして、os.close(1)'file'ハンドルを使用するために開かれる前に、OSの最初のファイルハンドルを盗むことができる他のスレッドが実行されていないことに注意してください。
Alex Robinson

2
そのos.O_WRONLY | os.O_CREAT ... Eはありません。
ジェフシェフィールド


@ Ch'marr O_CREATEではなくO_CREATです。
quant_dev 2016年

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

Pythonの3.4から始まっているredirect_stdoutcontextlib
ウォルターTross


3

ユダプラウィラの回答のバリエーションを次に示します。

  • 実装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、これは私が私のプロジェクトの1つで使用する方法を理解する別の方法です。置換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の小さなコメントが、Pythonのトリックがどれほど巧妙で、彼らが受け取った賛成票の数に関係なく、提供されている他のすべての人よりはるかに好ましい解決策の唯一の言及であることに驚いています。Ryanのコメントに加えて、tmuxはGNU screenの素晴らしい代替手段です。

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


4
編集後に表示された質問の部分には良い答えですが、タイトルの質問には回答しません(ほとんどの人はgoogleからタイトルのためにここに来ます)
jfs

0

他の言語(Cなど)で書かれたプログラムは、ターミナルから切り離す(およびゾンビプロセスを防ぐ)ために特別な魔法(ダブルフォークと呼ばれる)を行う必要があります。だから、私はそれらをエミュレートすることが最善の解決策だと思います。

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

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


ええと...私が自分自身の二重分岐を管理するプロセスのファンだとは言えません。これは非常に一般的なイディオムであり、注意しないと間違ってコーディングしやすくなります。プロセスをフォアグラウンドで実行するように記述し、システムバックグラウンドタスクマネージャー(systemdupstart)または他のユーティリティ(daemon(1))を使用して、フォークするボイラープレートを処理することをお勧めします。
Lucretiel、2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.