web-dev-qa-db-ja.com

Python Popen:stdoutへの書き込みとログファイルの同時記録

Popenを使用して、その標準出力と標準エラー出力をログファイルに継続的に書き込むシェルスクリプトを呼び出しています。ログファイルを連続して(画面に)同時に出力する方法、または、シェルスクリプトがログファイルとstdoutの両方に同時に書き込むようにする方法はありますか?

私は基本的にPythonでこのようなことをしたいです:

cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script

繰り返しますが、これはstderr/stdoutを一緒にteeにパイプし、stdoutと私のログファイルの両方に書き込みます。

Pythonでstdoutとstderrをログファイルに書き込む方法を知っています。私が立ち往生しているのは、これらを画面に複製する方法です:

subprocess.Popen("cat file", Shell=True, stdout=logfile, stderr=logfile)

もちろん、私はこのようなことをすることができますが、ティーとシェルファイル記述子のリダイレクトなしでこれを行う方法はありますか?:

subprocess.Popen("cat file 2>&1 | tee -a logfile", Shell=True)
35
imagineerThat

パイプを使用して、プログラムの標準出力からデータを読み取り、必要なすべての場所に書き込むことができます。

_import sys
import subprocess

logfile = open('logfile', 'w')
proc=subprocess.Popen(['cat', 'file'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
    sys.stdout.write(line)
    logfile.write(line)
proc.wait()
_

[〜#〜] update [〜#〜]

python 3では、_universal_newlines_パラメーターはパイプの使用方法を制御します。 Falseの場合、パイプ読み取りはbytesオブジェクトを返し、文字列を取得するためにデコードする必要がある場合があります(たとえば、line.decode('utf-8'))。 Trueの場合、pythonがデコードを行います

バージョン3.3で変更:universal_newlinesがTrueの場合、クラスはlocale.getpreferredencoding()の代わりにエンコーディングlocale.getpreferredencoding(False)を使用します。この変更の詳細については、io.TextIOWrapperクラスを参照してください。

35
tdelaney

エミュレートするには:teeコマンドを呼び出さずにsubprocess.call("command 2>&1 | tee -a logfile", Shell=True)

#!/usr/bin/env python2
from subprocess import Popen, PIPE, STDOUT

p = Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1)
with p.stdout, open('logfile', 'ab') as file:
    for line in iter(p.stdout.readline, b''):
        print line,  #NOTE: the comma prevents duplicate newlines (softspace hack)
        file.write(line)
p.wait()

バッファリングの問題を修正するには(出力が遅れる場合)、 Python:subprocess.communicate() からストリーミング入力を読み取ります。

Python 3バージョン:

#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE, STDOUT

with Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1) as p, \
     open('logfile', 'ab') as file:
    for line in p.stdout: # b'\n'-separated lines
        sys.stdout.buffer.write(line) # pass bytes as is
        file.write(line)
13
jfs

対話型アプリケーションの場合は、バイト単位で端末に書き込みます

このメソッドは、stdoutに到達したバイトをすぐに書き込みます。これにより、特にインタラクティブアプリケーションの場合、teeの動作をより厳密にシミュレートできます。

main.py

#!/usr/bin/env python3
import os
import subprocess
import sys
with subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc, \
        open('logfile.txt', 'bw') as logfile:
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            logfile.write(byte)
            # logfile.flush()
        else:
            break
exit_status = proc.returncode

sleep.py

#!/usr/bin/env python3
import sys
import time
for i in range(10):
    print(i)
    sys.stdout.flush()
    time.sleep(1)

最初に、非対話式の健全性チェックを実行できます。

./main.py ./sleep.py

そして、リアルタイムで標準出力にカウントされることがわかります。

次に、対話型テストの場合、次を実行できます。

./main.py bash

次に、入力した文字は、入力するとすぐに端末に表示されます。これは、対話型アプリケーションにとって非常に重要です。実行すると次のようになります。

bash | tee logfile.txt

また、出力をすぐにouptutファイルに表示したい場合は、次を追加することもできます。

logfile.flush()

しかし、teeはこれを行いません。パフォーマンスが低下するのではないかと心配しています。これを簡単にテストできます:

tail -f logfile.txt

関連質問: サブプロセスコマンドからのライブ出力

Ubuntu 18.04でテスト済み、Python 3.6.7。