pythonスクリプトのサブプロセス 'stdoutとstdinを同じファイルに送りたいのですが、2つのソースからの行を区別できるようにする方法がわかりません(たとえば、接頭辞として感嘆符付きのstderrからの行。)
私の特定のケースでは、サブプロセスのライブモニタリングは必要ありません。実行中のPythonスクリプトは、その実行の終了を待つことができます。
tsk = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
subprocess.STDOUT
は、すべてのstderr出力をstdoutにルーティングして、2つのストリームを結合するようにサブプロセスに指示する特別なフラグです。
ところで、selectはWindowsにpoll()を持っていません。サブプロセスはファイルハンドル番号のみを使用し、ファイル出力オブジェクトのwriteメソッドを呼び出しません。
出力をキャプチャするには、次のようにします。
logfile = open(logfilename, 'w')
while tsk.poll() is None:
line = tsk.stdout.readline()
logfile.write(line)
最近、この問題に取り組む必要があることに気づきました。ほとんどの場合、正しく機能していると感じるものを取得するのに時間がかかったので、ここにあります。 (これには、pythonロガーを介して出力を処理するという素晴らしい副作用もあります。これはStackoverflowに関するもう1つの一般的な質問です)。
コードは次のとおりです。
import sys
import logging
import subprocess
from threading import Thread
logging.basicConfig(stream=sys.stdout,level=logging.INFO)
logging.addLevelName(logging.INFO+2,'STDERR')
logging.addLevelName(logging.INFO+1,'STDOUT')
logger = logging.getLogger('root')
pobj = subprocess.Popen(['python','-c','print 42;bargle'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def logstream(stream,loggercb):
while True:
out = stream.readline()
if out:
loggercb(out.rstrip())
else:
break
stdout_thread = Thread(target=logstream,
args=(pobj.stdout,lambda s: logger.log(logging.INFO+1,s)))
stderr_thread = Thread(target=logstream,
args=(pobj.stderr,lambda s: logger.log(logging.INFO+2,s)))
stdout_thread.start()
stderr_thread.start()
while stdout_thread.isAlive() and stderr_thread.isAlive():
pass
出力は次のとおりです。
STDOUT:root:42
STDERR:root:Traceback (most recent call last):
STDERR:root: File "<string>", line 1, in <module>
STDERR:root:NameError: name 'bargle' is not defined
サブプロセス呼び出しを置き換えて、好きなことを行うことができます。実行するpythonを、stdoutとstderrの両方に出力することがわかっているコマンドで選択しました。重要なのは、stderrとstdoutをそれぞれ読み取ることです。別のスレッド。そうしないと、もう一方のスレッドで読み取る準備ができているデータがある間、一方の読み取りをブロックする可能性があります。
プロセスをインタラクティブに実行した場合とほぼ同じ順序を取得するためにインターリーブしたい場合は、シェルが行うことを実行し、stdin/stdoutをポーリングして、ポーリングする順序で書き込む必要があります。
これはあなたが望むものに沿って何かをするいくつかのコードです-この場合、stdout/stderrをロガー情報/エラーストリームに送信します。
tsk = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
poll = select.poll()
poll.register(tsk.stdout,select.POLLIN | select.POLLHUP)
poll.register(tsk.stderr,select.POLLIN | select.POLLHUP)
pollc = 2
events = poll.poll()
while pollc > 0 and len(events) > 0:
for event in events:
(rfd,event) = event
if event & select.POLLIN:
if rfd == tsk.stdout.fileno():
line = tsk.stdout.readline()
if len(line) > 0:
logger.info(line[:-1])
if rfd == tsk.stderr.fileno():
line = tsk.stderr.readline()
if len(line) > 0:
logger.error(line[:-1])
if event & select.POLLHUP:
poll.unregister(rfd)
pollc = pollc - 1
if pollc > 0: events = poll.poll()
tsk.wait()
現時点では、サブプロセスが-u
フラグを受け入れるPythonスクリプトでない場合、他のすべての回答は子サブプロセス側のバッファリングを処理しません。 "Q :パイプ(popen())を使用しないのはなぜですか? "pexpectドキュメント 。
一部のCstdioベース(-u
)プログラムのFILE*
フラグをシミュレートするには、 stdbuf
を試すことができます。
これを無視すると、出力は適切にインターリーブされず、次のようになります。
stderr
stderr
...large block of stdout including parts that are printed before stderr...
次のクライアントプログラムで試してみてください。-u
フラグがある場合とない場合の違いに注意してください(['stdbuf', '-o', 'L', 'child_program']
も出力を修正します)。
#!/usr/bin/env python
from __future__ import print_function
import random
import sys
import time
from datetime import datetime
def tprint(msg, file=sys.stdout):
time.sleep(.1*random.random())
print("%s %s" % (datetime.utcnow().strftime('%S.%f'), msg), file=file)
tprint("stdout1 before stderr")
tprint("stdout2 before stderr")
for x in range(5):
tprint('stderr%d' % x, file=sys.stderr)
tprint("stdout3 after stderr")
Linuxでは、 pty
を使用して、サブプロセスがインタラクティブに実行される場合と同じ動作を得ることができます。たとえば、変更された @ T.Rojanの回答 :
import logging, os, select, subprocess, sys, pty
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
master_fd, slave_fd = pty.openpty()
p = subprocess.Popen(args,stdout=slave_fd, stderr=subprocess.PIPE, close_fds=True)
with os.fdopen(master_fd) as stdout:
poll = select.poll()
poll.register(stdout, select.POLLIN)
poll.register(p.stderr,select.POLLIN | select.POLLHUP)
def cleanup(_done=[]):
if _done: return
_done.append(1)
poll.unregister(p.stderr)
p.stderr.close()
poll.unregister(stdout)
assert p.poll() is not None
read_write = {stdout.fileno(): (stdout.readline, logger.info),
p.stderr.fileno(): (p.stderr.readline, logger.error)}
while True:
events = poll.poll(40) # poll with a small timeout to avoid both
# blocking forever and a busy loop
if not events and p.poll() is not None:
# no IO events and the subprocess exited
cleanup()
break
for fd, event in events:
if event & select.POLLIN: # there is something to read
read, write = read_write[fd]
line = read()
if line:
write(line.rstrip())
Elif event & select.POLLHUP: # free resources if stderr hung up
cleanup()
else: # something unexpected happened
assert 0
sys.exit(p.wait()) # return child's exit code
Stderrは常にバッファリングされていない/ラインバッファリングされており、stdoutはインタラクティブモードでラインバッファリングされていることを前提としています。全行のみが読み取られます。出力に終了していない行がある場合、プログラムはブロックする可能性があります。
次のような独自のハンドラーを作成することをお勧めします(テストされていません。アイデアを理解していただければ幸いです)。
class my_buffer(object):
def __init__(self, fileobject, prefix):
self._fileobject = fileobject
self.prefix = prefix
def write(self, text):
return self._fileobject.write('%s %s' % (self.prefix, text))
# delegate other methods to fileobject if necessary
log_file = open('log.log', 'w')
my_out = my_buffer(log_file, 'OK:')
my_err = my_buffer(log_file, '!!!ERROR:')
p = subprocess.Popen(command, stdout=my_out, stderr=my_err, Shell=True)
コマンドの実行後に、stdout/errをファイルに書き込むことができます。以下の例では、pickle化を使用しているので、stdout/errを区別するために特別な解析を行わなくても読み取ることができ、ある時点で出口コードとコマンド自体を複製できます。
import subprocess
import cPickle
command = 'ls -altrh'
outfile = 'log.errout'
pipe = subprocess.Popen(command, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, Shell = True)
stdout, stderr = pipe.communicate()
f = open(outfile, 'w')
cPickle.dump({'out': stdout, 'err': stderr},f)
f.close()