Đọc đầu vào phát trực tuyến từ subprocess.communicate ()


83

Tôi đang sử dụng Python subprocess.communicate()để đọc stdout từ một quy trình chạy trong khoảng một phút.

Làm cách nào tôi có thể in ra từng dòng của quy trình đó stdouttheo kiểu truyền trực tuyến, để tôi có thể thấy đầu ra khi nó được tạo, nhưng vẫn bị chặn khi quá trình kết thúc trước khi tiếp tục?

subprocess.communicate() xuất hiện để cung cấp tất cả các đầu ra cùng một lúc.


Câu trả lời:


44

Xin lưu ý, tôi nghĩ rằng phương pháp của JF Sebastian (bên dưới) là tốt hơn.


Đây là một ví dụ đơn giản (không kiểm tra lỗi):

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

Nếu lskết thúc quá nhanh, thì vòng lặp while có thể kết thúc trước khi bạn đọc tất cả dữ liệu.

Bạn có thể lấy phần còn lại trong stdout theo cách này:

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

1
lược đồ này có trở thành nạn nhân của sự cố chặn bộ đệm mà tài liệu python đề cập đến không?
Heinrich Schmetterling

@Heinrich, vấn đề chặn bộ đệm không phải là điều tôi hiểu rõ. Tôi tin rằng (chỉ từ googling xung quanh) rằng sự cố này chỉ xảy ra nếu bạn không đọc từ stdout (và stderr?) Bên trong vòng lặp while. Vì vậy, tôi nghĩ rằng mã trên là ổn, nhưng tôi không thể nói chắc chắn.
unutbu

1
Điều này thực sự gặp phải một vấn đề chặn, một vài năm trước, tôi đã không gặp khó khăn trong đó readline sẽ chặn 'cho đến khi nó có một dòng mới ngay cả khi proc đã kết thúc. Tôi không nhớ giải pháp, nhưng tôi nghĩ nó có liên quan gì đó đến việc thực hiện các lần đọc trên một chuỗi công nhân và chỉ lặp lại while proc.poll() is None: time.sleep(0)hoặc một cái gì đó có hiệu lực. Về cơ bản- bạn cần đảm bảo rằng dòng mới đầu ra là điều cuối cùng mà quá trình thực hiện (vì bạn không thể cho trình thông dịch có thời gian lặp lại) hoặc bạn cần làm điều gì đó "lạ mắt".
dash-tom-bang

@Heinrich: Alex Martelli viết về cách tránh bế tắc ở đây: stackoverflow.com/questions/1445627/…
unutbu

6
Việc chặn bộ đệm đơn giản hơn đôi khi nghe có vẻ: khối cha đợi con thoát ra + khối con chờ cha mẹ đọc và giải phóng một số không gian trong đường ống liên lạc đã đầy = deadlock. Nó là đơn giản. Đường ống càng nhỏ thì khả năng xảy ra càng cao.
MarcH

160

Để nhận từng dòng đầu ra của quy trình con ngay khi quy trình con xóa bộ đệm stdout của nó:

#!/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()được sử dụng để đọc các dòng ngay khi chúng được viết để giải quyết lỗi đọc trước trong Python 2 .

Nếu stdout của quy trình con sử dụng bộ đệm khối thay vì bộ đệm dòng ở chế độ không tương tác (dẫn đến sự chậm trễ trong đầu ra cho đến khi bộ đệm của trẻ đầy hoặc được xóa rõ ràng bởi trẻ) thì bạn có thể cố gắng buộc đầu ra không có bộ đệm bằng cách sử dụng pexpect, ptymodule hay unbuffer, stdbuf, scripttiện ích , xem Q: Tại sao không chỉ cần sử dụng một ống (popen ())?


Đây là mã 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='')

Lưu ý: Không giống như Python 2 xuất ra các bytestrings của quy trình con như nguyên trạng; Python 3 sử dụng chế độ văn bản (đầu ra của cmd được giải mã bằng cách sử dụng locale.getpreferredencoding(False)mã hóa).


b '' nghĩa là gì?
Aaron

4
b''là một bytesnghĩa đen trong Python 2.7 và Python 3.
jfs

2
@JinghaoShi: bufsize=1có thể tạo ra sự khác biệt nếu bạn cũng viết (sử dụng p.stdin) vào quy trình con, ví dụ, nó có thể giúp tránh bế tắc trong khi thực hiện pexpecttrao đổi tương tác ( giống như) - giả sử không có vấn đề đệm trong chính quy trình con. Nếu bạn chỉ đang đọc thì như tôi đã nói, sự khác biệt chỉ nằm ở hiệu suất: nếu không phải vậy thì bạn có thể cung cấp một ví dụ mã hoàn chỉnh tối thiểu cho thấy điều đó không?
jfs

1
@ealeon: vâng. Nó yêu cầu các kỹ thuật có thể đọc stdout / stderr đồng thời trừ khi bạn hợp nhất stderr vào stdout (bằng cách chuyển stderr=subprocess.STDOUTtới Popen()). Xem thêm, giải pháp phân luồng hoặc asyncio được liên kết ở đó.
jfs

2
@saulspatz nếu stdout=PIPEkhông ghi lại kết quả đầu ra (bạn vẫn nhìn thấy nó trên màn hình) thì chương trình của bạn có thể in ra stderr hoặc trực tiếp tới terminal. Để hợp nhất stdout & stderr, hãy vượt qua stderr=subprocess.STDOUT(xem nhận xét trước của tôi). Để chụp ảnh đầu ra được in trực tiếp tới tty của bạn, bạn có thể sử dụng các giải pháp pexpect, pty. . Đây là một ví dụ mã phức tạp hơn .
jfs

6

Tôi tin rằng cách đơn giản nhất để thu thập đầu ra từ một quy trình theo kiểu phát trực tuyến là như sau:

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

Hàm readline()or read()chỉ nên trả về một chuỗi trống trên EOF, sau khi quá trình kết thúc - nếu không nó sẽ chặn nếu không có gì để đọc ( readline()bao gồm cả dòng mới, vì vậy đối với các dòng trống, nó trả về "\ n"). Điều này tránh cần phải có một communicate()cuộc gọi cuối cùng khó xử sau vòng lặp.

Trên các tệp có dòng rất dài read()có thể thích hợp hơn để giảm mức sử dụng bộ nhớ tối đa - số lượng được truyền vào nó là tùy ý, nhưng việc loại trừ nó dẫn đến việc đọc toàn bộ đầu ra đường ống cùng một lúc, điều này có lẽ không mong muốn.


4
data = proc.stdout.read()khối cho đến khi tất cả dữ liệu được đọc. Bạn có thể nhầm lẫn nó với nó có os.read(fd, maxsize)thể trở lại sớm hơn (ngay khi có bất kỳ dữ liệu nào).
jfs

Bạn nói đúng, tôi đã nhầm. Tuy nhiên, nếu một số lượng byte hợp lý được chuyển như một đối số read()thì nó hoạt động tốt và tương tự như vậy cũng readline()hoạt động tốt miễn là độ dài dòng tối đa là hợp lý. Đã cập nhật câu trả lời của tôi cho phù hợp.
D Coetzee


3

Nếu bạn chỉ đơn giản là cố gắng chuyển đầu ra trong thời gian thực, thì thật khó để đơn giản hơn thế này:

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'])

Xem tài liệu cho subprocess.check_call () .

Nếu bạn cần xử lý đầu ra, chắc chắn, hãy lặp lại nó. Nhưng nếu bạn không, chỉ cần giữ nó đơn giản.

Chỉnh sửa: JF Sebastian chỉ ra rằng cả mặc định cho các tham số stdout và stderr đều chuyển qua sys.stdout và sys.stderr, và điều này sẽ không thành công nếu sys.stdout và sys.stderr đã được thay thế (giả sử, để bắt đầu ra trong thử nghiệm).


Nó sẽ không hoạt động nếu sys.stdouthoặc sys.stderrđược thay thế bằng các đối tượng giống tệp không có tệpno () thực. Nếu sys.stdout, sys.stderrkhông được thay thế sau đó nó thậm chí còn đơn giản hơn: subprocess.check_call(args).
jfs

Cảm ơn! Tôi đã nhận ra sự khác biệt của việc thay thế sys.stdout / stderr, nhưng bằng cách nào đó không bao giờ nhận ra rằng nếu bạn bỏ qua các đối số, nó sẽ chuyển stdout và stderr đến đúng vị trí. Tôi thích call()hơn check_call()trừ khi tôi muốn CalledProcessError.
Nate

python -mthis: "Lỗi không bao giờ được trôi qua một cách âm thầm. Trừ khi được im lặng một cách rõ ràng." đó là lý do tại sao mã ví dụ nên thích check_call()hơn call().
jfs

Heh. Rất nhiều chương trình tôi kết thúc call()trả về mã lỗi nonzero trong điều kiện không lỗi, bởi vì chúng quá khủng khiếp. Vì vậy, trong trường hợp của chúng tôi, mã lỗi khác không thực sự không phải là lỗi.
Nate

Đúng. Có những chương trình như vậy grepcó thể trả về trạng thái thoát khác 0 ngay cả khi không có lỗi - chúng là những trường hợp ngoại lệ. Theo mặc định, trạng thái thoát 0 cho biết thành công.
jfs

1
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'))

1
luôn luôn tốt nếu giải thích giải pháp của bạn làm gì chỉ để mọi người hiểu rõ hơn
DaFois

2
Bạn nên cân nhắc sử dụng shlex.split(myCommand)thay thế myCommand.split(). Nó cũng tôn trọng các khoảng trắng trong các đối số được trích dẫn.
UtahJarhead

0

Thêm một giải pháp python3 khác với một vài thay đổi nhỏ:

  1. Cho phép bạn bắt mã thoát của quy trình trình bao (Tôi đã không thể lấy mã thoát khi sử dụng with cấu trúc)
  2. Ngoài ra các ống dẫn ra trong thời gian thực
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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.