簡體   English   中英

從子進程實時捕獲標准輸出

[英]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 進程來調用你的程序。
  • 調用進程時,參數作為列表傳遞。 python 中的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。

另見https://unix.stackexchange.com/questions/25372/

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM