2014-09-09 15 views
6

Tôi đang chạy một tập lệnh thông qua mô-đun quy trình con của Python. Hiện tại tôi sử dụng:Hiển thị đầu ra của tiến trình con để xuất chuẩn và chuyển hướng nó

p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
result = p.communicate() 

Sau đó, tôi in kết quả vào giá trị xuất chuẩn. Điều này là tất cả tốt nhưng như kịch bản mất một thời gian dài để hoàn thành, tôi muốn sản lượng thời gian thực từ kịch bản để stdout là tốt. Lý do tôi dẫn đầu ra là vì tôi muốn phân tích nó.

+0

liên quan: [Python: đọc luồng đầu vào từ 'subprocess.communicate()'] (http://stackoverflow.com/q/2715847/4279) – jfs

+0

liên quan: [Python subprocess có đầu ra của trẻ em để tập tin và thiết bị đầu cuối?] (Http://stackoverflow.com/q/4984428/4279) – jfs

+0

Bạn có thể thử sử dụng subprocess.call (['/ path/to/script']) nếu bạn không cần truy cập vào tất cả các tùy chọn cấp thấp hơn của Popen. Đầu ra sẽ được truyền theo mặc định. – Lukeclh

Trả lời

11

Để lưu stdout của subprocess thành biến để xử lý tiếp và display it while the child process is running as it arrives:

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

with Popen('/path/to/script', stdout=PIPE, bufsize=1, 
      universal_newlines=True) as p, StringIO() as buf: 
    for line in p.stdout: 
     print(line, end='') 
     buf.write(line) 
    output = buf.getvalue() 
rc = p.returncode 

Để lưu stdout cả của tiến trình con và stderr là phức tạp hơn bởi vì bạn nên consume both streams concurrently to avoid a deadlock:

stdout_buf, stderr_buf = StringIO(), StringIO() 
rc = teed_call('/path/to/script', stdout=stdout_buf, stderr=stderr_buf, 
       universal_newlines=True) 
output = stdout_buf.getvalue() 
... 

nơi teed_call() is define here.


Cập nhật: đây là a simpler asyncio version.


Phiên bản cũ:

Dưới đây là một giải pháp đơn luồng dựa trên child_process.py example from tulip:

import asyncio 
import sys 
from asyncio.subprocess import PIPE 

@asyncio.coroutine 
def read_and_display(*cmd): 
    """Read cmd's stdout, stderr while displaying them as they arrive.""" 
    # start process 
    process = yield from asyncio.create_subprocess_exec(*cmd, 
      stdout=PIPE, stderr=PIPE) 

    # read child's stdout/stderr concurrently 
    stdout, stderr = [], [] # stderr, stdout buffers 
    tasks = { 
     asyncio.Task(process.stdout.readline()): (
      stdout, process.stdout, sys.stdout.buffer), 
     asyncio.Task(process.stderr.readline()): (
      stderr, process.stderr, sys.stderr.buffer)} 
    while tasks: 
     done, pending = yield from asyncio.wait(tasks, 
       return_when=asyncio.FIRST_COMPLETED) 
     assert done 
     for future in done: 
      buf, stream, display = tasks.pop(future) 
      line = future.result() 
      if line: # not EOF 
       buf.append(line) # save for later 
       display.write(line) # display in terminal 
       # schedule to read the next line 
       tasks[asyncio.Task(stream.readline())] = buf, stream, display 

    # wait for the process to exit 
    rc = yield from process.wait() 
    return rc, b''.join(stdout), b''.join(stderr) 

Kịch bản chạy '/path/to/script lệnh và đọc từng dòng cả stdout của nó & stderr đồng thời. Các dòng này được in theo thứ tự stdout/stderr của cha và tương ứng và được lưu dưới dạng chuỗi xác nhận để xử lý trong tương lai. Để chạy read_and_display() coroutine, chúng ta cần một vòng lặp sự kiện:

import os 

if os.name == 'nt': 
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows 
    asyncio.set_event_loop(loop) 
else: 
    loop = asyncio.get_event_loop() 
try: 
    rc, *output = loop.run_until_complete(read_and_display("/path/to/script")) 
    if rc: 
     sys.exit("child failed with '{}' exit code".format(rc)) 
finally: 
    loop.close() 
1

p.communicate() waits for the subprocess to complete và sau đó trả lại toàn bộ đầu ra cùng một lúc.

Bạn đã thử một cái gì đó như thế này thay vào đó, nơi bạn đọc dòng đầu ra của tiến trình con?

p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
for line in p.stdout: 
    # do something with this individual line 
    print line 
+1

nếu quá trình con tạo ra đủ đầu ra để lấp đầy bộ đệm ống đệm hệ điều hành (65K trên máy của tôi) thì nó bị treo. Bạn cũng nên dùng 'p.stderr' - đồng thời. Do lỗi đọc trước, 'cho dòng trong p.stdout' sẽ in trong các cụm. Bạn có thể sử dụng 'for line in iter (p.stdout.readline, b '')' để thay thế. 'dòng in' sẽ in hai dòng mới. Bạn có thể sử dụng 'print line,' (lưu ý: dấu phẩy), để tránh nó. – jfs

+0

Điểm tuyệt vời về việc tiêu thụ 'stderr'. Tôi đã giả định rằng một vài dòng đệm sẽ không phải là một vấn đề trong một dòng dữ liệu dài dòng, nhưng đó là một cái gì đó để xem xét là tốt. –

+1

* "tập lệnh mất nhiều thời gian để hoàn thành" * - điều đó có nghĩa là nếu tập lệnh viết tiến trình thành stderr thì nó có thể * có thể dừng. – jfs

0

Các Popen.communicate doc nêu rõ:

Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited. 

https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate

Vì vậy, nếu bạn cần sản lượng thời gian thực, bạn cần phải sử dụng một cái gì đó như thế này:

stream_p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

while stream_line in stream_p: 
    #Parse it the way you want 
    print stream_line 
Các vấn đề liên quan