Nhận đầu ra thời gian thực bằng cách sử dụng quy trình con


135

Tôi đang cố gắng viết một kịch bản trình bao bọc cho một chương trình dòng lệnh (svnadmin xác minh) sẽ hiển thị một chỉ báo tiến trình tốt đẹp cho hoạt động. Điều này đòi hỏi tôi phải có thể nhìn thấy từng dòng đầu ra từ chương trình được gói ngay khi nó là đầu ra.

Tôi hình dung rằng tôi chỉ thực hiện chương trình bằng cách sử dụng subprocess.Popen, sử dụng stdout=PIPE, sau đó đọc từng dòng khi nó đến và hành động theo nó. Tuy nhiên, khi tôi chạy đoạn mã sau, đầu ra dường như được đệm ở đâu đó, khiến nó xuất hiện thành hai đoạn, dòng 1 đến 332, sau đó là 333 đến 439 (dòng đầu ra cuối cùng)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

Sau khi xem tài liệu về quy trình con một chút, tôi phát hiện ra bufsizetham sốPopen , vì vậy tôi đã thử đặt bufsize thành 1 (đệm mỗi dòng) và 0 (không có bộ đệm), nhưng dường như không có giá trị nào thay đổi cách phân phối các dòng.

Tại thời điểm này tôi đã bắt đầu nắm bắt được ống hút, vì vậy tôi đã viết vòng lặp đầu ra sau:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

nhưng nhận được kết quả tương tự.

Có thể lấy đầu ra chương trình 'thời gian thực' của chương trình được thực hiện bằng quy trình con không? Có một số tùy chọn khác trong Python tương thích về phía trước (không exec*) không?


1
Bạn đã thử bỏ qua sydout=PIPEđể quá trình con ghi trực tiếp vào bàn điều khiển của bạn, bỏ qua quá trình cha?
S.Lott

5
Điều này là tôi muốn đọc đầu ra. Nếu nó là đầu ra trực tiếp đến bàn điều khiển, làm thế nào tôi có thể làm điều đó? Ngoài ra, tôi không muốn người dùng thấy đầu ra từ chương trình được bao bọc, chỉ là đầu ra của tôi.
Chris Lieb

Vậy thì tại sao màn hình "thời gian thực"? Tôi không nhận được trường hợp sử dụng.
S.Lott

8
Đừng dùng shell = True. Nó không cần thiết gọi vỏ của bạn. Sử dụng p = Popen (['svnadmin', 'verify', '/ var / svn / repos / config'], stdout = PIPE, stderr = STDOUT) thay vào đó
nosklo

2
@ S.Lott Về cơ bản, svnadmin xác minh in một dòng đầu ra cho mỗi sửa đổi được xác minh. Tôi muốn tạo ra một chỉ báo tiến độ tốt đẹp sẽ không gây ra số lượng đầu ra quá mức. Ví dụ như kiểu wget
Chris Lieb

Câu trả lời:


82

Tôi đã thử điều này, và vì một số lý do trong khi mã

for line in p.stdout:
  ...

Bộ đệm tích cực, các biến thể

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

không làm. Rõ ràng đây là một lỗi đã biết: http://bugs.python.org/su3907 (Vấn đề hiện đã được "Đóng" kể từ ngày 29 tháng 8 năm 2018)


Đây không phải là mớ hỗn độn duy nhất trong các triển khai IO IO cũ. Đây là lý do tại sao Py2.6 và Py3k kết thúc với một thư viện IO hoàn toàn mới.
Tim Lin

3
Mã này sẽ bị hỏng nếu quy trình con trả về một dòng trống. Một giải pháp tốt hơn sẽ là sử dụngwhile p.poll() is None thay vì while Truevà loại bỏif not line
shoutuma

6
@exhuma: nó hoạt động tốt. readline trả về "\ n" trên một dòng trống, không đánh giá là đúng. nó chỉ trả về một chuỗi rỗng khi đường ống đóng lại, sẽ là khi quá trình con kết thúc.
Alice Purcell

1
@Dave Đối với ref trong tương lai: in dòng utf-8 bằng py2 + với print(line.decode('utf-8').rstrip()).
Jonathan Komar

3
Ngoài ra để có thời gian thực đọc kết quả đầu ra của quá trình, bạn sẽ cần nói với python rằng bạn KHÔNG muốn có bất kỳ bộ đệm nào. Python thân mến chỉ cho tôi đầu ra trực tiếp. Và đây là cách thực hiện: Bạn cần đặt biến môi trường PYTHONUNBUFFERED=1. Điều này đặc biệt hữu ích cho các kết quả đầu ra là vô hạn
George Pligoropoulos

38
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

1
@nbro có lẽ vì p.stdout.close()chưa rõ.
anatoly techtonik

1
@nbro có lẽ vì mã được đưa ra mà không có lời giải thích ...: /
Aaron Hall

3
B '' này là về cái gì?
ManuelSchneid3r

29

Bạn có thể hướng đầu ra của quy trình con đến các luồng trực tiếp. Ví dụ đơn giản:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

Điều này cho phép bạn cũng có được nội dung sau khi thực tế trong .communicate()? Hoặc là nội dung bị mất cho các luồng stderr / stdout cha mẹ?
theferrit32

Không, không có communicate()phương pháp trên trở lại CompletedProcess. Ngoài ra, capture_outputlà loại trừ lẫn nhau với stdoutstderr.
Aidan Feldman

20

Bạn có thể thử điều này:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Nếu bạn sử dụng readline thay vì đọc, sẽ có một số trường hợp thông báo đầu vào không được in. Hãy thử nó với một lệnh yêu cầu một đầu vào nội tuyến và tự mình xem.


Có, sử dụng readline () sẽ dừng in (ngay cả khi gọi sys.stdout.flush ())
Đánh dấu Ma

3
Đây có phải là để treo vô thời hạn? Tôi muốn một giải pháp nhất định cũng bao gồm mã soạn sẵn để chỉnh sửa vòng lặp khi quá trình con ban đầu được thực hiện. Xin lỗi tôi cho dù tôi có nhìn vào nó bao nhiêu lần đi chăng nữa, quy trình con vân vân là thứ tôi không bao giờ có thể làm việc được.
ThorSummoner

1
Tại sao phải kiểm tra '' khi trong Python chúng ta chỉ có thể sử dụng nếu không ra?
Greg Bell

2
đây là giải pháp tốt nhất cho các công việc dài hạn. nhưng nó nên sử dụng không phải là Không và không! = Không có. Bạn không nên sử dụng! = Với Không có.
Cari

Là stderr cũng được hiển thị bởi điều này?
Pieter Vogelaar

7

Quá trình phát trực tuyến stdin và stdout với asyncio trong bài đăng trên blog Python của Kevin McCarthy chỉ ra cách thực hiện với asyncio:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

điều này hoạt động với một sửa đổi nhỏ cho mã được đăng
Jeef

Xin chào @Jeef bạn có thể chỉ ra cách khắc phục để tôi có thể cập nhật câu trả lời không?
Pablo

Xin chào, điều đó đã làm việc cho tôi nhưng tôi đã phải thêm vào như sau để loại bỏ một số thông báo lỗi: import nest_asyncio; nest_asyncio.apply()và sử dụng lệnh shell, tức là process = await create_subprocess_shell(*command, stdout=PIPE, stderr=PIPE, shell=True)thay vì process = await create_subprocess_exec(...). Chúc mừng!
dùng319436

4

Vấn đề đầu ra thời gian thực đã được giải quyết: Tôi đã gặp phải vấn đề tương tự trong Python, trong khi nắm bắt đầu ra thời gian thực từ chương trình c. Tôi đã thêm " fflush (stdout) ;" trong mã C của tôi. Nó làm việc cho tôi. Đây là đoạn mã

<< Chương trình C >>

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

<< Chương trình Python >>

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

<< OUTPUT >> In: Đếm 1 In: Đếm 2 In: Đếm 3

Hy vọng nó giúp.

~ sairam


1
Đây là điều duy nhất thực sự có ích. Tôi đã sử dụng cùng một mã ( flush(stdout)) trong C ++. Cảm ơn!
Gerhard Hagerer

Tôi đã gặp vấn đề tương tự với một kịch bản python gọi một kịch bản python khác là một quy trình con. Trên các bản in quy trình con, "flush" là cần thiết (in ("xin chào", flush = True) trong python 3). Ngoài ra, rất nhiều ví dụ trên vẫn còn (2020) python 2, đây là python 3, vì vậy +1
smajtkst

3

Tôi gặp vấn đề tương tự một lúc sau. Giải pháp của tôi là bỏ đi lặp lại readphương thức, nó sẽ trả về ngay lập tức ngay cả khi quy trình con của bạn chưa thực hiện xong, v.v.


3

Tùy thuộc vào trường hợp sử dụng, bạn cũng có thể muốn tắt bộ đệm trong chính quy trình con.

Nếu quy trình con sẽ là một quy trình Python, bạn có thể thực hiện việc này trước cuộc gọi:

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

Hoặc thay thế vượt qua điều này trong envđối số để Popen.

Mặt khác, nếu bạn đang dùng Linux / Unix, bạn có thể sử dụng stdbufcông cụ này. Ví dụ như:

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

Xem thêm ở đây về stdbufhoặc các tùy chọn khác.

(Xem thêm ở đây để có câu trả lời tương tự.)


2

Tôi đã sử dụng giải pháp này để có được đầu ra thời gian thực trên một quy trình con. Vòng lặp này sẽ dừng lại ngay khi quá trình hoàn thành không cần phải có câu lệnh break hoặc vòng lặp vô hạn có thể.

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

5
Có thể là điều này sẽ thoát khỏi vòng lặp mà không có bộ đệm đầu ra trống rỗng?
jayjay

Tôi đã xem xét rất nhiều cho một câu trả lời phù hợp mà không bị treo khi hoàn thành! Tôi thấy đây là một giải pháp bằng cách thêm vào if out=='': breaksauout = sub_process...
Sos

2

Tìm thấy chức năng "plug-and-play" này tại đây . Làm việc như người ở!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

1
Việc bổ sung stderr=subprocess.STDOUTthực sự giúp ích rất nhiều trong việc thu thập dữ liệu phát trực tuyến. Tôi đang nâng cao nó.
khan

1
Thịt bò chính ở đây dường như đến từ câu trả lời được chấp nhận
tripleee

2

Bạn có thể sử dụng một trình vòng lặp trên mỗi byte trong đầu ra của quy trình con. Điều này cho phép cập nhật nội tuyến (các dòng kết thúc bằng '\ r' ghi đè lên dòng đầu ra trước đó) từ quy trình con:

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

2

Trong Python 3.x, quá trình có thể bị treo vì đầu ra là một mảng byte thay vì một chuỗi. Hãy chắc chắn rằng bạn giải mã nó thành một chuỗi.

Bắt đầu từ Python 3.6, bạn có thể thực hiện bằng tham số encodingtrong Trình tạo Popen . Ví dụ đầy đủ:

process = subprocess.Popen(
    'my_command',
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    shell=True,
    encoding='utf-8',
    errors='replace'
)

while True:
    realtime_output = process.stdout.readline()

    if realtime_output == '' and process.poll() is not None:
        break

    if realtime_output:
        print(realtime_output.strip(), flush=True)

Lưu ý rằng mã này chuyển hướng stderr đến stdoutxử lý các lỗi đầu ra .


1

Sử dụng pexect [ http://www.noah.org/wiki/Pevelopect ] với các đường dẫn không chặn sẽ giải quyết vấn đề này. Nó xuất phát từ thực tế là các đường ống được đệm, và do đó, đầu ra của ứng dụng của bạn bị đệm bởi đường ống, do đó bạn không thể có được đầu ra đó cho đến khi bộ đệm lấp đầy hoặc quá trình chết.


0

Giải pháp hoàn chỉnh:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

1
Vì bạn đang sử dụng universal_newlines=Truetrên các Popen()cuộc gọi, bạn có lẽ không cần phải đặt xử lý của riêng bạn trong số họ trong, quá - đó là toàn bộ điểm của các tùy chọn.
martineau

1
Có vẻ phức tạp không cần thiết. Nó không giải quyết vấn đề đệm. Xem các liên kết trong câu trả lời của tôi .
JFS

Đây là cách duy nhất tôi có thể nhận đầu ra tiến độ rsync trong thời gian thực (- outbuf = L)! cảm ơn
Mohammadhzp

0

Đây là bộ xương cơ bản mà tôi luôn sử dụng cho việc này. Nó giúp bạn dễ dàng thực hiện thời gian chờ và có thể xử lý các quy trình treo không thể tránh khỏi.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

0

(Giải pháp này đã được thử nghiệm với Python 2.7.15)
Bạn chỉ cần sys.stdout.flush () sau mỗi dòng đọc / ghi:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()

0

Rất ít câu trả lời gợi ý python 3.x hoặc pthon 2.x, Dưới đây mã sẽ hoạt động cho cả hai.

 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
    stdout = []
    while True:
        line = p.stdout.readline()
        if not isinstance(line, (str)):
            line = line.decode('utf-8')
        stdout.append(line)
        print (line)
        if (line == '' and p.poll() != None):
            break
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.