python - 終了 - subprocess run




Pythonは順序を保持しながら、サブプロセスstdoutとstderrから別々に読み取ります (5)

出力ストリームとエラーストリームを読み取ろうとしているpythonサブプロセスがあります。 現在は動作していますが、 stdout からの読み取りが終了した後にのみ stderr から読み取ることができます。 これは次のようなものです。

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_iterator = iter(process.stdout.readline, b"")
stderr_iterator = iter(process.stderr.readline, b"")

for line in stdout_iterator:
    # Do stuff with line
    print line

for line in stderr_iterator:
    # Do stuff with line
    print line

ご覧のとおり、 stderr forループは、 stdout ループが完了するまで開始できません。 行の正しい順序で両方から読み取ることができるようにこれを変更するにはどうすればよいですか?

明確にするために 、行が stdout または stderr から来たのかどうかを知る必要があります。これは、コード内で行が異なる方法で処理されるためです。


pythonのドキュメントによると

Popen.stdout stdout引数がPIPEの場合、この属性は子プロセスからの出力を提供するファイルオブジェクトです。 それ以外の場合は、なしです。

Popen.stderr stderr引数がPIPEの場合、この属性は、子プロセスからのエラー出力を提供するファイルオブジェクトです。 それ以外の場合は、なしです。

以下のサンプルはあなたが望むことをすることができます

test.py

print "I'm stdout"

raise Exception("I'm Error")

printer.py

import subprocess

p = subprocess.Popen(['python', 'test.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

print "Normal"
std_lines = p.stdout.readlines()
for line in std_lines:
    print line.rstrip()

print "Error"
stderr_lines = p.stderr.readlines()
for line in stderr_lines:
    print line.rstrip()

出力:

Normal
I'm stdout

Error
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise Exception("I'm Error")
Exception: I'm Error

これは私のために動作します(Windows): https://github.com/waszil/subpiper : https://github.com/waszil/subpiper

from subpiper import subpiper

def my_stdout_callback(line: str):
    print(f'STDOUT: {line}')

def my_stderr_callback(line: str):
    print(f'STDERR: {line}')

my_additional_path_list = [r'c:\important_location']

retcode = subpiper(cmd='echo magic',
                   stdout_callback=my_stdout_callback,
                   stderr_callback=my_stderr_callback,
                   add_path_list=my_additional_path_list)

子プロセスがstderrで十分な出力を生成する場合(私のLinuxマシンでは最大100KB)、質問のコードはデッドロックする可能性があります。

stdoutとstderrの両方から個別に読み取ることができる communicate() メソッドがあります。

from subprocess import Popen, PIPE

process = Popen(command, stdout=PIPE, stderr=PIPE)
output, err = process.communicate()

子プロセスの実行中にストリームを読み取る必要がある場合、移植可能なソリューションはスレッドを使用することです(テストされていません):

from subprocess import Popen, PIPE
from threading import Thread
from Queue import Queue # Python 2

def reader(pipe, queue):
    try:
        with pipe:
            for line in iter(pipe.readline, b''):
                queue.put((pipe, line))
    finally:
        queue.put(None)

process = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=1)
q = Queue()
Thread(target=reader, args=[process.stdout, q]).start()
Thread(target=reader, args=[process.stderr, q]).start()
for _ in range(2):
    for source, line in iter(q.get, None):
        print "%s: %s" % (source, line),

見る:

  • Python:subprocess.communicate()からストリーミング入力を読み取ります
  • Pythonのsubprocess.PIPEでの非ブロッキング読み取り
  • Pythonサブプロセスは、子の出力をファイルとターミナルに取得しますか?

私は ずっと前に これをするために何かを書きました。 Python 3にはまだ移植していませんが、それほど難しくないはずです(パッチは受け入れられました!)

スタンドアロンで実行すると、多くの異なるオプションが表示されます。 いずれの場合でも、stdoutとstderrを区別できます。


selectors に基づくソリューションがありますが、順序を保持し、可変長文字(単一の文字も含む)をストリーミングします。

read1() Theは、 read() 代わりに read1() を使用すること read1()

import selectors
import subprocess
import sys

p = subprocess.Popen(
    "python random_out.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

sel = selectors.DefaultSelector()
sel.register(p.stdout, selectors.EVENT_READ)
sel.register(p.stderr, selectors.EVENT_READ)

while True:
    for key, _ in sel.select():
        data = key.fileobj.read1().decode()
        if not data:
            exit()
        if key.fileobj is p.stdout:
            print(data, end="")
        else:
            print(data, end="", file=sys.stderr)

テストプログラムが必要な場合は、これを使用します。

import sys
from time import sleep


for i in range(10):
    print(f" x{i} ", file=sys.stderr, end="")
    sleep(0.1)
    print(f" y{i} ", end="")
    sleep(0.1)




stderr