簡體   English   中英

從 subprocess.communicate() 讀取流輸入

[英]Read streaming input from subprocess.communicate()

我正在使用 Python 的subprocess.communicate()從運行大約一分鍾的進程中讀取標准輸出。

如何以流方式打印出該進程stdout每一行,以便我可以看到生成的輸出,但在繼續之前仍然阻止進程終止?

subprocess.communicate()似乎一次給出所有輸出。

要在子進程刷新其 stdout 緩沖區后立即逐行獲取子進程的輸出:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()用於在寫入行后立即讀取行以解決Python 2 中的預讀錯誤

如果子進程的標准輸出在非交互模式下使用塊緩沖而不是行緩沖(這會導致輸出延遲,直到子進程的緩沖區已滿或被子進程顯式刷新),那么您可以嘗試使用pexpectpty模塊unbufferstdbufscript實用程序,請參閱Q:為什么不直接使用管道 (popen())?


這是 Python 3 代碼:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

注意:不像 Python 2 那樣輸出子進程的字節串; Python 3 使用文本模式(cmd 的輸出使用locale.getpreferredencoding(False)編碼進行解碼)。

請注意,我認為JF Sebastian 的方法(如下)更好。


這是一個簡單的示例(不檢查錯誤):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

如果ls結束得太快,那么 while 循環可能會在您讀取所有數據之前結束。

您可以通過這種方式在標准輸出中捕獲剩余部分:

output = proc.communicate()[0]
print output,

我相信以流方式從進程中收集輸出的最簡單方法是這樣的:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

readline()read()函數應該只在 EOF 上返回一個空字符串,在進程終止后 - 否則它會阻塞,如果沒有任何東西可以讀取( readline()包括換行符,所以在空行上,它返回“ \\n")。 這避免了在循環之后進行笨拙的最終communicate()調用的需要。

在具有很長行的文件上read()可能更適合減少最大內存使用量 - 傳遞給它的數字是任意的,但排除它會導致一次讀取整個管道輸出,這可能是不可取的。

如果您想要非阻塞方法,請不要使用process.communicate() 如果將subprocess.Popen()參數stdoutPIPE ,則可以從process.stdout讀取並使用process.poll()檢查進程是否仍在運行。

如果您只是想實時傳遞輸出,那么很難比這更簡單:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

請參閱subprocess.check_call()文檔

如果你需要處理輸出,當然,循環它。 但如果你不這樣做,就保持簡單。

編輯: JF Sebastian指出 stdout 和 stderr 參數的默認值傳遞給 sys.stdout 和 sys.stderr,如果 sys.stdout 和 sys.stderr 被替換(例如,用於捕獲測試)。

myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

添加另一個 python3 解決方案,並進行一些小的更改:

  1. 允許您捕獲 shell 進程的退出代碼(我在使用with構造時無法獲取退出代碼)
  2. 還實時輸出標准錯誤
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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