[英]catching stdout in realtime from subprocess
我想在 Windows 中 subprocess.Popen 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()
subprocess
一些經驗法則。
shell=True
。 它不必要地調用一個額外的 shell 進程來調用你的程序。sys.argv
是一個列表,C 中的argv
也是一個列表。所以你將一個列表傳遞給Popen
來調用子進程,而不是一個字符串。stderr
重定向到PIPE
。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 檢測到它連接到管道而不是終端時,它可能會緩沖其輸出。 這是默認行為 - 當連接到管道時,程序必須顯式刷新標准輸出以獲得實時結果,否則標准 C 庫將緩沖。
要對此進行測試,請嘗試運行它:
cmd = [sys.executable, 'test_out.py']
並創建一個包含以下內容的test_out.py
文件:
import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")
執行該子進程應該給你“你好”並等待 10 秒鍾,然后再給“世界”。 如果上面的 python 代碼發生這種情況而不是rsync
,那意味着rsync
本身正在緩沖輸出,所以你運氣不好。
一種解決方案是使用pexpect
東西直接連接到pty
。
我知道這是一個老話題,但現在有一個解決方案。 使用選項 --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())
根據用例,您可能還想禁用子流程本身的緩沖。
如果子進程將是一個 Python 進程,您可以在調用之前執行此操作:
os.environ["PYTHONUNBUFFERED"] = "1"
或者將其在env
參數中傳遞給Popen
。
否則,如果您使用的是 Linux/Unix,則可以使用stdbuf
工具。 例如:
cmd = ["stdbuf", "-oL"] + cmd
另請參閱此處了解stdbuf
或其他選項。
在 Linux 上,我遇到了擺脫緩沖的同樣問題。 我最終使用“stdbuf -o0”(或者,從expect 中取消緩沖)來擺脫PIPE 緩沖。
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout
然后我可以在標准輸出上使用 select.select。
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)
將阻塞,直到子進程關閉其標准輸出或退出。
你的問題是:
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
迭代器本身有額外的緩沖。
嘗試這樣做:
while True:
line = p.stdout.readline()
if not line:
break
print line
你不能讓標准輸出無緩沖地打印到管道(除非你可以重寫打印到標准輸出的程序),所以這是我的解決方案:
將標准輸出重定向到未緩沖的 sterr。 '<cmd> 1>&2'
應該這樣做。 打開進程如下: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
您無法區分 stdout 或 stderr,但您會立即獲得所有輸出。
希望這可以幫助任何人解決這個問題。
為了避免緩存輸出,您可能想嘗試 pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
try:
child.expect('\n')
print(child.before)
except pexpect.EOF:
break
PS :我知道這個問題已經很老了,仍然提供對我有用的解決方案。
PPS :從另一個問題得到這個答案
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' 。
將 rsync 進程中的 stdout 更改為無緩沖。
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)
我注意到沒有提到使用臨時文件作為中間文件。 下面通過輸出到臨時文件來解決緩沖問題,並允許您解析來自 rsync 的數據而無需連接到 pty。 我在 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...
如果你在一個線程中運行這樣的東西並將 ffmpeg_time 屬性保存在一個方法的屬性中以便你可以訪問它,它會工作得很好我得到這樣的輸出:輸出就像你在 tkinter 中使用線程一樣
input = 'path/input_file.mp4'
output = 'path/input_file.mp4'
command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True)
for line in self.process.stdout:
reg = re.search('\d\d:\d\d:\d\d', line)
ffmpeg_time = reg.group(0) if reg else ''
print(ffmpeg_time)
在 Python 3 中,這里有一個解決方案,它從命令行中取出一個命令,並在接收到字符串時實時提供經過良好解碼的字符串。
接收器( receiver.py
):
import subprocess
import sys
cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
for line in p.stdout:
print("received: {}".format(line.rstrip().decode("utf-8")))
可以生成實時輸出( dummy_out.py
)的示例簡單程序:
import time
import sys
for i in range(5):
print("hello {}".format(i))
sys.stdout.flush()
time.sleep(1)
輸出:
$python receiver.py python dummy_out.py
received: hello 0
received: hello 1
received: hello 2
received: hello 3
received: hello 4
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.