print - subprocess stdout python3




コマンドを実行し、stdout、stderrをターミナルのようにほぼリアルタイムで個別に取得します (2)

実行中のプログラムのstdoutとstderrは別々にログに記録できます。

stdoutとstderrの両方が同じ pty 移動し、その後それらを分離する方法がないため、 pexpect 使用できません。

実行中のプログラムの標準出力と標準エラーをほぼリアルタイムで表示できるため、子プロセスがハングした場合にユーザーが確認できます。 (つまり、実行が完了するのを待たずに、stdout / stderrをユーザーに出力します)

サブプロセスの出力がttyではない場合 、ブロックバッファリング 使用する可能性が高い ため、出力があまり多く ない場合、「リアルタイム」ではありません。 たとえば、バッファが4Kで、次に親子プロセスが4K文字を出力してバッファがオーバーフローするか、明示的に(サブプロセス内で)フラッシュされるまで、Pythonプロセスは何も認識しません。 このバッファは子プロセス内にあり、外部から管理する標準的な方法はありません。 command 1 | command2 stdioバッファーとパイプバッファーを示す写真を次に示します command 1 | command2 command 1 | command2 シェルパイプライン:

実行中のプログラムは、Pythonを介して実行されていることを認識しないため、予期しないことを行いません(リアルタイムで出力する代わりに出力をチャンクしたり、出力を表示するために端末を要求するために終了したりします)。

出力がパイプにリダイレクトされた場合(Pythonで stdout=PIPE を使用する場合)、各出力行をできるだけ早くフラッシュするのではなく、子プロセスがその出力をチャンクする可能性が高いようです。 これは、デフォルトの threading または asyncioソリューション があなたの場合のように機能しないことを意味します。

回避策にはいくつかのオプションがあります。

  • コマンドは、ブロックバッファリングを無効にするために、 grep --line-bufferedpython -u などのコマンドライン引数を受け入れる場合があります。

  • stdbuf は一部のプログラムで機能します。 つまり、上記のスレッド化またはasyncioソリューションを使用して ['stdbuf', '-oL', '-eL'] + command を実行できます。stdout、stderrを個別に取得し、行がほぼ実時間:

    #!/usr/bin/env python3
    import os
    import sys
    from select import select
    from subprocess import Popen, PIPE
    
    with Popen(['stdbuf', '-oL', '-e0', 'curl', 'www.google.com'],
               stdout=PIPE, stderr=PIPE) as p:
        readable = {
            p.stdout.fileno(): sys.stdout.buffer, # log separately
            p.stderr.fileno(): sys.stderr.buffer,
        }
        while readable:
            for fd in select(readable, [], [])[0]:
                data = os.read(fd, 1024) # read available
                if not data: # EOF
                    del readable[fd]
                else: 
                    readable[fd].write(data)
                    readable[fd].flush()
  • 最後に、2つの pty pty + select ソリューションを試すことができ select

    #!/usr/bin/env python3
    import errno
    import os
    import pty
    import sys
    from select import select
    from subprocess import Popen
    
    masters, slaves = zip(pty.openpty(), pty.openpty())
    with Popen([sys.executable, '-c', r'''import sys, time
    print('stdout', 1) # no explicit flush
    time.sleep(.5)
    print('stderr', 2, file=sys.stderr)
    time.sleep(.5)
    print('stdout', 3)
    time.sleep(.5)
    print('stderr', 4, file=sys.stderr)
    '''],
               stdin=slaves[0], stdout=slaves[0], stderr=slaves[1]):
        for fd in slaves:
            os.close(fd) # no input
        readable = {
            masters[0]: sys.stdout.buffer, # log separately
            masters[1]: sys.stderr.buffer,
        }
        while readable:
            for fd in select(readable, [], [])[0]:
                try:
                    data = os.read(fd, 1024) # read available
                except OSError as e:
                    if e.errno != errno.EIO:
                        raise #XXX cleanup
                    del readable[fd] # EIO means EOF on some systems
                else:
                    if not data: # EOF
                        del readable[fd]
                    else:
                        readable[fd].write(data)
                        readable[fd].flush()
    for fd in masters:
        os.close(fd)

    stdout、stderrに異なる pty を使用した場合の副作用はわかりません。 たとえば、 stderr=PIPE を設定し、 masters[1] 代わりに p.stderr.fileno() を使用するなど、単一のptyで十分かどうかを試すことができます。 sh ソースのコメントは、 stderr not in {STDOUT, pipe} 場合に問題があることを示唆しています

私はPythonで他のプログラムを実行する方法を見つけようとしています:

  1. 実行中のプログラムのstdoutとstderrは別々にログに記録できます。
  2. 実行中のプログラムの標準出力と標準エラーをほぼリアルタイムで表示できるため、子プロセスがハングした場合にユーザーが確認できます。 (つまり、実行が完了するのを待たずに、stdout / stderrをユーザーに出力します)
  3. ボーナス基準:実行中のプログラムは、Pythonを介して実行されていることを知らないため、予期しないことを行いません(リアルタイムで出力する代わりに出力をチャンクする、出力を表示するために端末を要求するために終了する) 。 この小さな基準は、私が思うにptyを使用する必要があることを意味します。

ここに私が今まで持っているものがあります...方法1:

def method1(command):
    ## subprocess.communicate() will give us the stdout and stderr sepurately, 
    ## but we will have to wait until the end of command execution to print anything.
    ## This means if the child process hangs, we will never know....
    proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
    stdout, stderr = proc.communicate() # record both, but no way to print stdout/stderr in real-time
    print ' ######### REAL-TIME ######### '
    ########         Not Possible
    print ' ########## RESULTS ########## '
    print 'STDOUT:'
    print stdout
    print 'STDOUT:'
    print stderr

方法2

def method2(command):
    ## Using pexpect to run our command in a pty, we can see the child's stdout in real-time,
    ## however we cannot see the stderr from "curl google.com", presumably because it is not connected to a pty?
    ## Furthermore, I do not know how to log it beyond writing out to a file (p.logfile). I need the stdout and stderr
    ## as strings, not files on disk! On the upside, pexpect would give alot of extra functionality (if it worked!)
    proc = pexpect.spawn('/bin/bash', ['-c', command])
    print ' ######### REAL-TIME ######### '
    proc.interact()
    print ' ########## RESULTS ########## '
    ########         Not Possible

方法3:

def method3(command):
    ## This method is very much like method1, and would work exactly as desired
    ## if only proc.xxx.read(1) wouldn't block waiting for something. Which it does. So this is useless.
    proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
    print ' ######### REAL-TIME ######### '
    out,err,outbuf,errbuf = '','','',''
    firstToSpeak = None
    while proc.poll() == None:
            stdout = proc.stdout.read(1) # blocks
            stderr = proc.stderr.read(1) # also blocks
            if firstToSpeak == None:
                if stdout != '': firstToSpeak = 'stdout'; outbuf,errbuf = stdout,stderr
                elif stderr != '': firstToSpeak = 'stderr'; outbuf,errbuf = stdout,stderr
            else:
                if (stdout != '') or (stderr != ''): outbuf += stdout; errbuf += stderr
                else:
                    out += outbuf; err += errbuf;
                    if firstToSpeak == 'stdout': sys.stdout.write(outbuf+errbuf);sys.stdout.flush()
                    else: sys.stdout.write(errbuf+outbuf);sys.stdout.flush()
                    firstToSpeak = None
    print ''
    print ' ########## RESULTS ########## '
    print 'STDOUT:'
    print out
    print 'STDERR:'
    print err

これらの方法を試すには、 import sys,subprocess,pexpectimport sys,subprocess,pexpect する必要があります

pexpectはpure-pythonであり、

sudo pip install pexpect

解決策はpythonのptyモジュールに関係すると思います-これはやや黒い芸術であり、使用方法を知っている人を見つけることができません。 おそらくSOは知っています:)ヘッズアップとして、テストコマンドとして「curl www.google.com」を使用することをお勧めします、それは何らかの理由でstderrにステータスを出力するからです:D

更新-1:
わかりましたので、ptyライブラリは人間の消費には適していません。 基本的に、ドキュメントはソースコードです。 非同期ではなくブロックしている提示されたソリューションは、ここでは機能しません。 Padraic CunninghamによるThreads / Queueメソッドはうまく機能しますが、ptyサポートを追加することはできません-それは「ダーティ」です(Freenodeの#pythonを引用するため)。 生産標準コードに適合する唯一のソリューションは、Twistedフレームワークを使用することであるようです。Twistedフレームワークは、シェルから呼び出されたようにプロセスを実行するブールスイッチとしてptyをサポートします。 ただし、プロジェクトにTwistedを追加するには、すべてのコードを完全に書き直す必要があります。 これは完全に残念です:/

更新-2:

2つの答えが提供されました。そのうちの1つは最初の2つの基準に対応しており、 Threads and Queue を使用してstdoutとstderrの両方が必要な場合にうまく機能します。 もう1つの答えは、ファイル記述子を読み取るための非ブロッキングメソッドである select と、Bashから直接実行されているかのように、実際の端末で実行されていると信じ込ませるプロセスを「トリック」する方法であるptyを使用し select 副作用がないかもしれません。 「正しい」方法は、状況と最初にサブ処理している理由に本当に依存するため、両方の回答を受け入れられることを望みますが、残念ながら、私は1つしか受け入れられませんでした。


JF Sebastianの答えは確かに問題の核心を解決しますが、私はPython 2.7を実行しています(元の基準にはありませんでした)ので、単にコードをカット/ペーストしたい他の疲れた旅行者にこれを投げるだけです。 私はまだこれを完全にテストしていませんが、試したすべてのコマンドで完全に動作するようです:) .decode( 'ascii')を.decode( 'utf-8')に変更することができます-まだそのビットをテストしていますでる。

#!/usr/bin/env python2.7
import errno
import os
import pty
import sys
from select import select
import subprocess
stdout = ''
stderr = ''
command = 'curl google.com ; sleep 5 ; echo "hey"'
masters, slaves = zip(pty.openpty(), pty.openpty())
p = subprocess.Popen(command, stdin=slaves[0], stdout=slaves[0], stderr=slaves[1], shell=True, executable='/bin/bash')
for fd in slaves: os.close(fd)

readable = { masters[0]: sys.stdout, masters[1]: sys.stderr }
try:
    print ' ######### REAL-TIME ######### '
    while readable:
        for fd in select(readable, [], [])[0]:
            try: data = os.read(fd, 1024)
            except OSError as e:
                if e.errno != errno.EIO: raise
                del readable[fd]
            finally:
                if not data: del readable[fd]
                else:
                    if fd == masters[0]: stdout += data.decode('ascii')
                    else: stderr += data.decode('ascii')
                    readable[fd].write(data)
                    readable[fd].flush()
except: pass
finally:
    p.wait()
    for fd in masters: os.close(fd)
    print ''
    print ' ########## RESULTS ########## '
    print 'STDOUT:'
    print stdout
    print 'STDERR:'
    print stderr




pty