web-dev-qa-db-ja.com

サブプロセスから標準出力をリアルタイムでキャッチ

Windowsでsubprocess.Popen() rsync.exeを実行し、Pythonで標準出力を出力します。

私のコードは動作しますが、ファイル転送が完了するまで進行状況をキャッチしません!各ファイルの進行状況をリアルタイムで印刷したい。

Python 3.1を使用するようになりました。IOの処理が改善されるはずだと聞いたからです。

import subprocess, time, os, sys

cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'


p = subprocess.Popen(cmd,
                     Shell=True,
                     bufsize=64,
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()
71
John A

subprocessの経験則。

  • Never use Shell=True。プログラムを呼び出すために、余分なシェルプロセスを不必要に呼び出します。
  • プロセスを呼び出すとき、引数はリストとして渡されます。 sys.argv in pythonはリストであり、Cのargvも同様です。したがって、listPopenに渡して呼び出します。文字列ではなく、サブプロセス。
  • 読んでいないときは、stderrPIPEにリダイレクトしないでください。
  • 書き込みを行っていないときにstdinをリダイレクトしないでください。

例:

import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]

p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, b''):
    print(">>> " + line.rstrip())

とは言っても、rsyncが端末ではなくパイプに接続されていることを検出すると、rsyncが出力をバッファリングする可能性があります。これはデフォルトの動作です-パイプに接続する場合、プログラムはリアルタイムの結果のためにstdoutを明示的にフラッシュする必要があります。そうしないと、標準Cライブラリがバッファリングします。

それをテストするには、代わりにこれを実行してみてください:

cmd = [sys.executable, 'test_out.py']

test_out.pyファイルの内容:

import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")

そのサブプロセスを実行すると、「Hello」が表示され、「World」が表示されるまで10秒待機します。上記のコードでpythonが発生し、rsyncでは発生しない場合は、rsync自体が出力をバッファリングしているため、運が悪いのです。

解決策は、ptyのようなものを使用して、pexpectに直接接続することです。

88
nosklo

私はこれが古いトピックであることを知っていますが、現在解決策があります。オプション--outbuf = Lを指定してrsyncを呼び出します。例:

cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
                     stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
    print '>>> {}'.format(line.rstrip())
32
Elvin

Linuxでは、バッファリングを取り除くという同じ問題がありました。私は最終的に「stdbuf -o0」(または、expectからアンバッファー)を使用して、PIPEバッファリングを取り除きました。

proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout

その後、stdoutでselect.selectを使用できます。

https://unix.stackexchange.com/questions/25372/ も参照してください

11
Ling
_for line in p.stdout:
  ...
_

次の改行まで常にブロックします。

「リアルタイム」動作の場合、次のような操作を行う必要があります。

_while True:
  inchar = p.stdout.read(1)
  if inchar: #neither empty string nor None
    print(str(inchar), end='') #or end=None to flush immediately
  else:
    print('') #flush for implicit line-buffering
    break
_

子プロセスが標準出力を閉じるか終了すると、whileループが残ります。 read()/read(-1)は、子プロセスが標準出力を閉じるか終了するまでブロックします。

8
IBue

あなたの問題は:

for line in p.stdout:
    print(">>> " + str(line.rstrip()))
    p.stdout.flush()

イテレータ自体には追加のバッファリングがあります。

このようにしてみてください:

while True:
  line = p.stdout.readline()
  if not line:
     break
  print line
7
zviadm

Stdoutをバッファなしでパイプに出力することはできません(stdoutに出力するプログラムを書き換えることができない限り)。ここに私の解決策を示します。

Stdoutをsterrにリダイレクトします。これはバッファリングされません。 _'<cmd> 1>&2'_はそれを行う必要があります。次のようにプロセスを開きます:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
stdoutまたはstderrと区別することはできませんが、すべての出力をすぐに取得できます。

これがこの問題に取り組む誰にも役立つことを願っています。

5
Erik

ユースケースによっては、サブプロセス自体のバッファリングを無効にすることもできます。

サブプロセスがPythonプロセスである場合、呼び出しの前にこれを行うことができます。

os.environ["PYTHONUNBUFFERED"] = "1"

または、これをenv引数でPopenに渡します。

それ以外の場合、Linux/Unixを使用している場合は、stdbufツールを使用できます。例えば。好む:

cmd = ["stdbuf", "-oL"] + cmd

here about stdbufまたはその他のオプションも参照してください。

4
Albert

出力のキャッシュを回避するには、pexpectを試してみてください。

child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
    try:
        child.expect('\n')
        print(child.before)
    except pexpect.EOF:
        break

[〜#〜] ps [〜#〜]:私はこの質問がかなり古いことを知っています。

[〜#〜] pps [〜#〜]:別の質問からこの回答を得た

2
nithin
    p = subprocess.Popen(command,
                                bufsize=0,
                                universal_newlines=True)

私はPythonでrsync用のGUIを書いていますが、同じ問題があります。この問題は、pyDocでこれを見つけるまで数日間悩みました。

Universal_newlinesがTrueの場合、ファイルオブジェクトstdoutおよびstderrは、ユニバーサル改行モードでテキストファイルとして開かれます。行は、 '\ n'(Unixの行末規則)、 '\ r'(古いMacintosh規則)、または '\ r\n'(Windows規則)のいずれかで終了できます。これらの外部表現はすべて、Pythonプログラムによって「\ n」として認識されます。

翻訳が進行中の場合、rsyncは「\ r」を出力するようです。

2
xmc

Rsyncプロセスの標準出力をバッファなしに変更します。

p = subprocess.Popen(cmd,
                     Shell=True,
                     bufsize=0,  # 0=unbuffered, 1=line-buffered, else buffer-size
                     stdin=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     stdout=subprocess.PIPE)
2
Will

中間ファイルとして一時ファイルを使用することに言及していないことに気付きました。以下は、一時ファイルに出力することでバッファリングの問題を回避し、ptyに接続せずにrsyncからのデータを解析できるようにします。 Linuxボックスで以下をテストしましたが、rsyncの出力はプラットフォーム間で異なる傾向があるため、出力を解析する正規表現は異なる場合があります。

import subprocess, time, tempfile, re

pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]

p = subprocess.Popen(cmd, stdout=pipe_output, 
                     stderr=subprocess.STDOUT)
while p.poll() is None:
    # p.poll() returns None while the program is still running
    # sleep for 1 second
    time.sleep(1)
    last_line =  open(file_name).readlines()
    # it's possible that it hasn't output yet, so continue
    if len(last_line) == 0: continue
    last_line = last_line[-1]
    # Matching to "[bytes downloaded]  number%  [speed] number:number:number"
    match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
    if not match_it: continue
    # in this case, the percentage is stored in match_it.group(1), 
    # time in match_it.group(2).  We could do something with it here...
0
MikeGM