đầu ra trực tiếp từ lệnh quy trình con


186

Tôi đang sử dụng tập lệnh python làm trình điều khiển cho mã thủy động lực học. Khi đến lúc chạy mô phỏng, tôi sử dụng subprocess.Popenđể chạy mã, thu thập đầu ra từ thiết bị xuất chuẩn và thiết bị xuất chuẩn thành subprocess.PIPE--- sau đó tôi có thể in (và lưu vào tệp nhật ký) thông tin đầu ra và kiểm tra xem có lỗi nào không . Vấn đề là, tôi không biết làm thế nào mã đang tiến triển. Nếu tôi chạy nó trực tiếp từ dòng lệnh, nó sẽ cho tôi đầu ra về việc lặp lại vào lúc nào, thời gian nào, bước thời gian tiếp theo là gì, v.v.

Có cách nào để lưu trữ đầu ra (để ghi nhật ký và kiểm tra lỗi) và cũng tạo ra đầu ra phát trực tiếp không?

Phần có liên quan trong mã của tôi:

ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
    print "RUN failed\n\n%s\n\n" % (errors)
    success = False

if( errors ): log_file.write("\n\n%s\n\n" % errors)

Ban đầu tôi đã chuyển run_commandqua teeđể một bản sao được gửi trực tiếp vào tệp nhật ký và luồng vẫn xuất trực tiếp đến thiết bị đầu cuối - nhưng theo cách đó tôi không thể lưu trữ bất kỳ lỗi nào (theo hiểu biết của tôi).


Biên tập:

Giải pháp tạm thời:

ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
    log_file.flush()

sau đó, trong một thiết bị đầu cuối khác, chạy tail -f log.txt(st log_file = 'log.txt').


1
Có lẽ bạn có thể sử dụng Popen.pollnhư trong câu hỏi Stack Overflow trước đó .
Paulo Almeida

Một số lệnh hiển thị chỉ báo tiến trình (ví dụ git:) chỉ làm như vậy nếu đầu ra của chúng là "thiết bị tty" (được kiểm tra qua libc isatty()). Trong trường hợp đó, bạn có thể phải mở một giả.

@torek là gì (giả-) tty?
DilithiumMatrix

2
Các thiết bị trên các hệ thống giống như Unix cho phép một quá trình giả vờ là người dùng trên một cổng nối tiếp. Đây là cách ssh (phía máy chủ) hoạt động, ví dụ. Xem thư viện pty python , và cũng khai thác .

Re giải pháp tạm thời: không cần gọi flush, và có cần phải đọc từ ống stderr nếu tiến trình con tạo ra stderr nhiều. Không có đủ chỗ trong một trường bình luận để giải thích điều này ...
torek

Câu trả lời:


169

Bạn có hai cách để làm điều này, bằng cách tạo một trình vòng lặp từ readhoặc các readlinehàm và thực hiện:

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for c in iter(lambda: process.stdout.read(1), ''):  # replace '' with b'' for Python 3
        sys.stdout.write(c)
        f.write(c)

hoặc là

import subprocess
import sys
with open('test.log', 'w') as f:  # replace 'w' with 'wb' for Python 3
    process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
    for line in iter(process.stdout.readline, ''):  # replace '' with b'' for Python 3
        sys.stdout.write(line)
        f.write(line)

Hoặc bạn có thể tạo một readervà một writertập tin. Truyền writercho Popenvà đọc từreader

import io
import time
import subprocess
import sys

filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
    process = subprocess.Popen(command, stdout=writer)
    while process.poll() is None:
        sys.stdout.write(reader.read())
        time.sleep(0.5)
    # Read the remaining
    sys.stdout.write(reader.read())

Bằng cách này, bạn sẽ có dữ liệu được viết test.logcũng như trên đầu ra tiêu chuẩn.

Ưu điểm duy nhất của cách tiếp cận tệp là mã của bạn không chặn. Vì vậy, bạn có thể làm bất cứ điều gì bạn muốn trong lúc này và đọc bất cứ khi nào bạn muốn từ readermột cách không chặn. Khi bạn sử dụng PIPE, readreadlinecác chức năng sẽ chặn cho đến khi một trong hai nhân vật một được ghi vào ống hoặc một dòng được ghi vào ống tương ứng.


1
Ugh :-) ghi vào một tập tin, đọc từ nó và ngủ trong vòng lặp? Cũng có khả năng quá trình sẽ kết thúc trước khi bạn đọc xong tệp.
Guy Sirton

13
Với Python 3, bạn cần iter(process.stdout.readline, b'')(tức là sentinel truyền cho iter cần phải là một chuỗi nhị phân, kể từ b'' != ''.
John Mellor

3
Đối với các luồng nhị phân, hãy làm điều này:for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
rrlamichhane

6
Thêm vào câu trả lời của @JohnMellor, trong Python 3 cần có những sửa đổi sau: process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
bergercookie

4
nhưng đầu ra không sống, phải không? theo kinh nghiệm của tôi, nó chỉ đợi cho đến khi quá trình hoàn thành thực thi và chỉ sau đó in ra bàn điều khiển. Liên kết -> stackoverflow.com/questions/30026045/ trên
denis631

91

Tóm tắt điều hành (hoặc phiên bản "tl; dr"): thật dễ dàng khi có nhiều nhất subprocess.PIPE, nếu không thì khó.

Có lẽ đã đến lúc giải thích một chút về việc làm thế nào subprocess.Popen.

(Hãy cẩn thận: đây là cho Python 2.x, mặc dù 3.x tương tự nhau và tôi khá mờ về biến thể Windows. Tôi hiểu rõ hơn về công cụ POSIX.)

Các Popenchức năng cần phải đối phó với zero-to-ba I / O suối, hơi cùng một lúc. Đây là những ký hiệu stdin, stdoutstderrnhư thường lệ.

Bạn có thể cung cấp:

  • None, chỉ ra rằng bạn không muốn chuyển hướng luồng. Nó sẽ kế thừa những thứ này như bình thường thay thế. Lưu ý rằng trên các hệ thống POSIX, ít nhất, điều này không có nghĩa là nó sẽ sử dụng thiết bị xuất chuẩn của Python sys.stdout, chỉ là thiết bị xuất chuẩn thực tế của Python ; xem bản demo ở cuối
  • Một intgiá trị. Đây là một mô tả tệp "thô" (ít nhất là trong POSIX). (Lưu ý bên: PIPESTDOUTthực sự intlà nội bộ, nhưng là mô tả "không thể", -1 và -2.)
  • Một luồng Stream thực sự, bất kỳ đối tượng với một filenophương thức. Popensẽ tìm mô tả cho luồng đó, sử dụng stream.fileno()và sau đó tiến hành như một intgiá trị.
  • subprocess.PIPE, chỉ ra rằng Python sẽ tạo ra một đường ống.
  • subprocess.STDOUT( stderrchỉ dành cho ): bảo Python sử dụng cùng một mô tả như cho stdout. Điều này chỉ có ý nghĩa nếu bạn cung cấp một Nonegiá trị (không ) cho stdout, và thậm chí sau đó, nó chỉ cần thiết nếu bạn đặt stdout=subprocess.PIPE. (Nếu không, bạn chỉ có thể cung cấp cùng một đối số bạn đã cung cấp stdout, ví dụ : Popen(..., stdout=stream, stderr=stream).)

Các trường hợp dễ nhất (không có đường ống)

Nếu bạn chuyển hướng không có gì (để cả ba làm Nonegiá trị mặc định hoặc cung cấp rõ ràng None), Pipethì điều đó khá dễ dàng. Nó chỉ cần quay vòng quá trình con và để nó chạy. Hoặc, nếu bạn chuyển hướng đến một phi PIPE-an inthoặc một dòng suối là fileno()-nó vẫn dễ dàng, như hệ điều hành làm tất cả công việc. Python chỉ cần loại bỏ tiến trình con, kết nối stdin, stdout và / hoặc stderr của nó với các mô tả tệp được cung cấp.

Trường hợp vẫn dễ dàng: một ống

Nếu bạn chỉ chuyển hướng một luồng, Pipevẫn có những thứ khá dễ dàng. Hãy chọn một luồng tại một thời điểm và xem.

Giả sử bạn muốn cung cấp một số stdin, nhưng hãy để stdoutstderrkhông chuyển hướng, hoặc đi đến một mô tả tập tin. Là tiến trình cha, chương trình Python của bạn chỉ cần sử dụng write()để gửi dữ liệu xuống đường ống. Bạn có thể tự làm điều này, vd:

proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc

hoặc bạn có thể truyền dữ liệu stdin tới proc.communicate(), sau đó thực stdin.writehiện như trên. Không có đầu ra nào quay trở lại nên communicate()chỉ có một công việc thực sự khác: nó cũng đóng đường ống cho bạn. (Nếu bạn không gọi, proc.communicate()bạn phải gọi proc.stdin.close()để đóng đường ống, để quy trình con biết rằng không có thêm dữ liệu nào đi qua.)

Giả sử bạn muốn chụp stdoutnhưng để lại stdinstderrmột mình. Một lần nữa, thật dễ dàng: chỉ cần gọi proc.stdout.read()(hoặc tương đương) cho đến khi không còn đầu ra nữa. Vì proc.stdout()là luồng I / O Python bình thường, bạn có thể sử dụng tất cả các cấu trúc bình thường trên nó, như:

for line in proc.stdout:

hoặc, một lần nữa, bạn có thể sử dụng proc.communicate(), mà chỉ đơn giản là làm read()cho bạn.

Nếu bạn chỉ muốn chụp stderr, nó hoạt động tương tự như với stdout.

Có thêm một mẹo trước khi mọi thứ trở nên khó khăn. Giả sử bạn muốn chụp stdoutvà cũng chụp stderrnhưng trên cùng một đường ống như thiết bị xuất chuẩn:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Trong trường hợp này, subprocess"gian lận"! Chà, nó phải làm điều này, vì vậy nó không thực sự gian lận: nó khởi động quy trình con với cả thiết bị xuất chuẩn và thiết bị xuất chuẩn của nó được đưa vào bộ mô tả đường ống (đơn) cung cấp lại cho quy trình cha mẹ (Python). Về phía phụ huynh, lại chỉ có một bộ mô tả ống duy nhất để đọc đầu ra. Tất cả đầu ra "stderr" hiển thị trong proc.stdoutvà nếu bạn gọi proc.communicate(), kết quả stderr (giá trị thứ hai trong tuple) sẽ Nonekhông phải là một chuỗi.

Các trường hợp khó: hai hoặc nhiều ống

Tất cả các vấn đề xảy ra khi bạn muốn sử dụng ít nhất hai ống. Trong thực tế, subprocessbản thân mã có bit này:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:

Nhưng, than ôi, ở đây chúng tôi đã thực hiện ít nhất hai, và có thể ba, các đường ống khác nhau, vì vậy count(None)lợi nhuận là 1 hoặc 0. Chúng tôi phải làm mọi thứ một cách khó khăn.

Trên Windows, điều này sử dụng threading.Threadđể tích lũy kết quả cho self.stdoutself.stderr, và có luồng gốc cung cấp self.stdindữ liệu đầu vào (và sau đó đóng đường ống).

Trên POSIX, điều này sử dụng pollnếu có, nếu không select, để tích lũy đầu ra và cung cấp đầu vào stdin. Tất cả điều này chạy trong tiến trình / luồng cha (đơn).

Chủ đề hoặc thăm dò ý kiến ​​/ chọn là cần thiết ở đây để tránh bế tắc. Ví dụ, giả sử rằng chúng tôi đã chuyển hướng cả ba luồng thành ba đường ống riêng biệt. Giả sử thêm rằng có một giới hạn nhỏ về số lượng dữ liệu có thể được nhét vào một đường ống trước khi quá trình viết bị đình chỉ, chờ quá trình đọc "dọn sạch" đường ống từ đầu kia. Chúng ta hãy đặt giới hạn nhỏ đó thành một byte đơn, chỉ để minh họa. (Đây thực tế là cách mọi thứ hoạt động, ngoại trừ giới hạn lớn hơn nhiều so với một byte.)

Nếu phụ huynh (Python) quá trình cố gắng để viết một vài byte-nói, 'go\n'để proc.stdin, byte đầu tiên đi vào và sau đó thứ hai gây ra quá trình Python để đình chỉ, chờ đợi tiến trình con để đọc các byte đầu tiên, đổ ống.

Trong khi đó, giả sử quy trình con quyết định in một câu thân thiện "Xin chào! Đừng hoảng sợ!" Lời chào. Nó Hđi vào đường ống cứng nhất của nó, nhưng enguyên nhân khiến nó bị đình chỉ, chờ cha mẹ đọc nó H, làm trống đường ống cứng.

Bây giờ chúng tôi bị kẹt: quá trình Python đang ngủ, chờ kết thúc bằng cách nói "đi" và quy trình con cũng đang ngủ, chờ kết thúc để nói "Xin chào! Đừng hoảng sợ!".

Các subprocess.Popenđang tránh vấn đề này với luồng-hoặc-select / thăm dò. Khi byte có thể đi qua các đường ống, họ đi. Khi họ không thể, chỉ một luồng (không phải toàn bộ quá trình) phải ngủ ngủ hoặc trong trường hợp chọn / thăm dò ý kiến, quy trình Python chờ đồng thời để "có thể ghi" hoặc "có sẵn dữ liệu", ghi vào stdin của quy trình chỉ khi có chỗ, và chỉ đọc thiết bị xuất chuẩn và / hoặc thiết bị xuất chuẩn khi dữ liệu đã sẵn sàng. Các proc.communicate()mã (thực sự _communicatenơi các trường hợp lông được xử lý) trở lại một lần toàn bộ dữ liệu stdin (nếu có) đã được gửi và tất cả dữ liệu stdout và / hoặc stderr đã được tích lũy.

Nếu bạn muốn đọc cả hai stdoutstderrtrên hai đường ống khác nhau (bất kể stdinchuyển hướng nào ), bạn cũng sẽ cần tránh bế tắc. Kịch bản bế tắc ở đây là khác nhau, nó xảy ra khi quy trình con viết một cái gì đó dài stderrtrong khi bạn lấy dữ liệu từ đó stdout, hoặc ngược lại, nhưng nó vẫn ở đó.


Bản demo

Tôi đã hứa sẽ chứng minh rằng, không được chuyển hướng, Python subprocesses ghi vào thiết bị xuất chuẩn bên dưới, không sys.stdout. Vì vậy, đây là một số mã:

from cStringIO import StringIO
import os
import subprocess
import sys

def show1():
    print 'start show1'
    save = sys.stdout
    sys.stdout = StringIO()
    print 'sys.stdout being buffered'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    in_stdout = sys.stdout.getvalue()
    sys.stdout = save
    print 'in buffer:', in_stdout

def show2():
    print 'start show2'
    save = sys.stdout
    sys.stdout = open(os.devnull, 'w')
    print 'after redirect sys.stdout'
    proc = subprocess.Popen(['echo', 'hello'])
    proc.wait()
    sys.stdout = save

show1()
show2()

Khi chạy:

$ python out.py
start show1
hello
in buffer: sys.stdout being buffered

start show2
hello

Lưu ý rằng thường trình đầu tiên sẽ thất bại nếu bạn thêm stdout=sys.stdout, vì một StringIOđối tượng không có fileno. Thứ hai sẽ bỏ qua hellonếu bạn thêm stdout=sys.stdoutsys.stdoutđã được chuyển hướng đến os.devnull.

(Nếu bạn chuyển hướng mô tả tệp của Python-1, quy trình con sẽ theo chuyển hướng đó. Cuộc open(os.devnull, 'w')gọi tạo ra một luồng có fileno()lớn hơn 2.)


Hừm. Bản demo của bạn dường như hiển thị ngược lại với yêu cầu cuối cùng. Bạn đang định hướng lại thiết bị xuất chuẩn của Python vào bộ đệm nhưng thiết bị xuất chuẩn của quy trình con vẫn đang chuyển đến bàn điều khiển. Làm thế nào là hữu ích? Tui bỏ lỡ điều gì vậy?
Guy Sirton

@GuySirton: các chương trình demo mà stdout subprocess (khi không rõ ràng dẫn đến sys.stdout) đi vào Python của stdout, không phải là trăn chương trình 's ( sys.) stdout. Mà tôi thừa nhận là một ... sự khác biệt kỳ lạ. Có cách nào tốt hơn để diễn đạt điều này?

đó là điều tốt để biết nhưng chúng tôi thực sự muốn nắm bắt đầu ra của quy trình con ở đây để thay đổi sys.stdout rất tuyệt nhưng tôi không giúp chúng tôi nghĩ như vậy. Quan sát tốt rằng giao tiếp phải được sử dụng một cái gì đó như select (), thăm dò ý kiến ​​hoặc chủ đề.
Guy Sirton

2
+1, giải thích tốt nhưng nó thiếu các ví dụ mã cụ thể. Đây là asynciomã dựa trên thực hiện "phần cứng" (nó xử lý đồng thời nhiều ống) theo cách di động . Bạn có thể so sánh nó với mã sử dụng nhiều luồng ( teed_call()) để làm như vậy .
jfs

Tôi đã thêm một triển khai với select ()
sivann

20

Chúng ta cũng có thể sử dụng trình lặp tệp mặc định để đọc thiết bị xuất chuẩn thay vì sử dụng cấu trúc iter với readline ().

import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
    sys.stdout.write(line)

Câu trả lời tao nhã nhất đây!
Niết

9
Giải pháp này không hiển thị trong thời gian thực. Nó chờ cho đến khi quá trình được thực hiện và hiển thị tất cả đầu ra cùng một lúc. Trong giải pháp của Viktor Kerkez, nếu "your_command" hiển thị liên tục, đầu ra sẽ theo sau dần dần, miễn là "your_command" thỉnh thoảng xả ra thiết bị xuất chuẩn (vì đường ống).
Eric H.

1
@Nir vì nó không sống.
melMass

Giải pháp này lặp lại trên bộ mô tả mặc định, vì vậy nó sẽ chỉ cập nhật khi một dòng cập nhật trong đầu ra. Để cập nhật dựa trên ký tự, bạn cần lặp lại phương thức read () như thể hiện trong giải pháp của Viktor. Nhưng đó là một quá mức cho trường hợp sử dụng của tôi.
Jughead

11

Nếu bạn có thể sử dụng các thư viện của bên thứ ba, Bạn có thể sử dụng một cái gì đó như sarge(tiết lộ: Tôi là người duy trì). Thư viện này cho phép không chặn truy cập vào các luồng đầu ra từ các quy trình con - nó nằm trên subprocessmô-đun.


Công việc tốt trên sà lan, BTW. Điều đó thực sự giải quyết được yêu cầu của OP, nhưng có thể hơi nặng tay cho trường hợp sử dụng đó.
đào sâu

Nếu bạn đang đề xuất một công cụ ít nhất là hiển thị một ví dụ về cách sử dụng cho trường hợp chính xác này.
Serhiy

4

Giải pháp 1: Đăng nhập stdoutstderrđồng thời trong thời gian thực

Một giải pháp đơn giản ghi nhật ký cả stdout AND stderr đồng thời, từng dòng một trong thời gian thực vào một tệp nhật ký.

import subprocess as sp
from concurrent.futures import ThreadPoolExecutor


def log_popen_pipe(p, stdfile):

    with open("mylog.txt", "w") as f:

        while p.poll() is None:
            f.write(stdfile.readline())
            f.flush()

        # Write the rest from the buffer
        f.write(stdfile.read())


with sp.Popen(["ls"], stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    with ThreadPoolExecutor(2) as pool:
        r1 = pool.submit(log_popen_pipe, p, p.stdout)
        r2 = pool.submit(log_popen_pipe, p, p.stderr)
        r1.result()
        r2.result()

Giải pháp 2: Một chức năng read_popen_pipes()cho phép bạn lặp qua cả hai ống (stdout / stderr), đồng thời trong thời gian thực

import subprocess as sp
from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

# The function in use:

with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):
        print(out_line, end='')
        print(err_line, end='')

    return p.poll()

3

Một giải pháp tốt nhưng "nặng ký" là sử dụng Twisted - xem phần dưới.

Nếu bạn sẵn sàng sống chỉ với thiết bị xuất chuẩn, những thứ đó sẽ hoạt động:

import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
   stdoutdata = popenobj.stdout.readline()
   if stdoutdata:
      sys.stdout.write(stdoutdata)
   else:
      break
print "Return code", popenobj.returncode

(Nếu bạn sử dụng read () nó sẽ cố đọc toàn bộ "tệp" không hữu ích, thì thứ chúng ta thực sự có thể sử dụng ở đây là thứ đọc tất cả dữ liệu trong ống ngay bây giờ)

Người ta cũng có thể cố gắng tiếp cận điều này với luồng, ví dụ:

import subprocess
import sys
import threading

popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)

def stdoutprocess(o):
   while True:
      stdoutdata = o.stdout.readline()
      if stdoutdata:
         sys.stdout.write(stdoutdata)
      else:
         break

t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode

Bây giờ chúng tôi có khả năng có thể thêm stderr bằng cách có hai luồng.

Tuy nhiên, xin lưu ý rằng các tài liệu của quy trình con không khuyến khích sử dụng trực tiếp các tệp này và khuyên bạn nên sử dụng communicate()(chủ yếu liên quan đến các khóa chết mà tôi nghĩ không phải là vấn đề ở trên) và các giải pháp hơi klunky nên có vẻ như mô-đun quy trình con không hoàn toàn công việc (cũng xem: http://www.python.org/dev/peps/pep-3145/ ) và chúng ta cần xem xét một cái gì đó khác.

Một giải pháp liên quan hơn là sử dụng Twisted như được hiển thị ở đây: https://twistedmatrix.com/document/11.1.0/core/howto/ Process.html

Cách bạn làm điều này với Twisted là tạo quy trình của bạn bằng cách sử dụngreactor.spawnprocess() và cung cấp ProcessProtocolquy trình sau đó xử lý đầu ra không đồng bộ. Mã Python mẫu bị xoắn ở đây: https://twistedmatrix.com/document/11.1.0/core/howto/listings/ process / process.py


Cảm ơn! Tôi chỉ cố gắng một cái gì đó như thế này (dựa trên bình luận @PauloAlmeida 's, nhưng cuộc gọi của tôi đến subprocess.Popen chặn - tức là nó chỉ nói đến trong khi vòng lặp khi nó trả về ...
DilithiumMatrix

1
Đó không phải là những gì đang xảy ra. Đó là vào vòng lặp while ngay sau đó chặn read()cuộc gọi cho đến khi tiến trình con thoát ra và tiến trình cha nhận được EOFtrên đường ống.
Alp

@Alp thú vị! nên nó là.
DilithiumMatrix

Vâng, tôi đã quá nhanh để đăng bài này. Nó thực sự không hoạt động đúng và không thể dễ dàng sửa chữa. trở lại bàn vẽ.
Guy Sirton

1
@zhermes: Vì vậy, vấn đề với read () là nó sẽ cố đọc toàn bộ đầu ra cho đến khi EOF không hữu ích. readline () giúp và có thể là tất cả những gì bạn cần (dòng thực sự dài cũng có thể là một vấn đề). Bạn cũng cần coi chừng việc đệm trong quá trình bạn khởi chạy ...
Guy Sirton

3

Ngoài tất cả những câu trả lời này, một cách tiếp cận đơn giản cũng có thể như sau:

process = subprocess.Popen(your_command, stdout=subprocess.PIPE)

while process.stdout.readable():
    line = process.stdout.readline()

    if not line:
        break

    print(line.strip())

Lặp lại luồng có thể đọc được miễn là nó có thể đọc được và nếu nó nhận được kết quả trống, hãy dừng nó lại.

Chìa khóa ở đây là readline()trả về một dòng (có \nở cuối) miễn là có đầu ra và trống nếu nó thực sự ở cuối.

Hy vọng điều này sẽ giúp được ai đó.


3

Dựa trên tất cả những điều trên tôi đề xuất một phiên bản sửa đổi một chút (python3):

  • while vòng lặp gọi readline (Giải pháp iter được đề xuất dường như chặn vĩnh viễn đối với tôi - Python 3, Windows 7)
  • được cấu trúc để việc xử lý dữ liệu đọc không cần phải được sao chép sau khi cuộc thăm dò trở lại không-None
  • stderr được dẫn vào thiết bị xuất chuẩn để cả hai đầu ra đầu ra đều được đọc
  • Đã thêm mã để nhận giá trị thoát của cmd.

Mã số:

import subprocess
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT, universal_newlines=True)
while True:
    rd = proc.stdout.readline()
    print(rd, end='')  # and whatever you want to do...
    if not rd:  # EOF
        returncode = proc.poll()
        if returncode is not None:
            break
        time.sleep(0.1)  # cmd closed stdout, but not exited yet

# You may want to check on ReturnCode here

Một returncodephần rất quan trọng trong trường hợp của tôi.
sao

2

Có vẻ như đầu ra được đệm dòng sẽ phù hợp với bạn, trong trường hợp đó, một cái gì đó như sau có thể phù hợp. (Hãy cẩn thận: chưa được kiểm tra.) Điều này sẽ chỉ cung cấp cho thiết bị xuất chuẩn của quy trình con trong thời gian thực. Nếu bạn muốn có cả thiết bị xuất chuẩn và thiết bị xuất chuẩn trong thời gian thực, bạn sẽ phải làm một cái gì đó phức tạp hơn select.

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
    line = proc.stdout.readline()
    print line
    log_file.write(line + '\n')
# Might still be data on stdout at this point.  Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
    print line
    log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...

2

Tại sao không đặt stdouttrực tiếp sys.stdout? Và nếu bạn cũng cần xuất ra một bản ghi, thì bạn có thể chỉ cần ghi đè phương thức ghi của f.

import sys
import subprocess

class SuperFile(open.__class__):

    def write(self, data):
        sys.stdout.write(data)
        super(SuperFile, self).write(data)

f = SuperFile("log.txt","w+")       
process = subprocess.Popen(command, stdout=f, stderr=f)

Điều đó sẽ không hoạt động: mô đun quy trình con rèn và đặt bộ stdoutmô tả tệp thành bộ mô tả tệp của đối tượng tệp được truyền. Phương thức viết sẽ không bao giờ được gọi (ít nhất đó là quy trình con làm gì cho thiết bị lỗi chuẩn, tôi đoán nó giống với thiết bị xuất chuẩn).
t.animal

2

Tất cả các giải pháp trên tôi đã thử thất bại trong việc tách đầu ra stderr và stdout, (nhiều ống) hoặc bị chặn vĩnh viễn khi bộ đệm ống OS đầy, điều này xảy ra khi lệnh bạn đang chạy quá nhanh (có một cảnh báo cho điều này trên python thăm dò ý kiến ​​() hướng dẫn của quy trình con). Cách đáng tin cậy duy nhất tôi tìm thấy là thông qua lựa chọn, nhưng đây là một giải pháp duy nhất:

import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    poller = select.poll()
    poller.register(p.stdout, select.POLLIN)
    poller.register(p.stderr, select.POLLIN)

    coutput=''
    cerror=''
    fdhup={}
    fdhup[p.stdout.fileno()]=0
    fdhup[p.stderr.fileno()]=0
    while sum(fdhup.values()) < len(fdhup):
        try:
            r = poller.poll(1)
        except select.error, err:
            if err.args[0] != EINTR:
                raise
            r=[]
        for fd, flags in r:
            if flags & (select.POLLIN | select.POLLPRI):
                c = os.read(fd, 1024)
                if rtoutput:
                    sys.stdout.write(c)
                    sys.stdout.flush()
                if fd == p.stderr.fileno():
                    cerror+=c
                else:
                    coutput+=c
            else:
                fdhup[fd]=1
    return p.poll(), coutput.strip(), cerror.strip()

Một cách khác là quay ra một luồng trên mỗi ống. Mỗi luồng có thể chặn I / O trên đường ống, mà không chặn (các) luồng khác. Nhưng điều này giới thiệu tập hợp các vấn đề riêng của mình. Tất cả các phương pháp đều có những phiền toái, bạn chỉ cần chọn một trong những phương pháp mà bạn thấy ít phiền toái nhất. :-)

2

Tương tự như các câu trả lời trước nhưng giải pháp sau đây đã làm việc cho tôi trên windows bằng Python3 để cung cấp một phương thức phổ biến để in và đăng nhập thời gian thực (get -realtime-output-using-python ):

def print_and_log(command, logFile):
    with open(logFile, 'wb') as f:
        command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)

        while True:
            output = command.stdout.readline()
            if not output and command.poll() is not None:
                f.close()
                break
            if output:
                f.write(output)
                print(str(output.strip(), 'utf-8'), flush=True)
        return command.poll()

2

Tôi nghĩ rằng subprocess.communicatephương pháp này hơi sai lệch: nó thực sự lấp đầy thiết bị xuất chuẩnthiết bị xuất chuẩn mà bạn chỉ định trong subprocess.Popen.

Tuy nhiên, đọc từ subprocess.PIPEmà bạn có thể cung cấp cho các subprocess.Popen's stdoutstderr thông số cuối cùng sẽ lấp đầy bộ đệm ống OS và bế tắc ứng dụng của bạn (đặc biệt nếu bạn đã nhiều quy trình / đề mà phải sử dụng subprocess).

Giải pháp đề xuất của tôi là cung cấp thiết bị xuất chuẩnthiết bị xuất chuẩn với các tệp - và đọc nội dung của tệp thay vì đọc từ bế tắc PIPE. Những tệp này có thể tempfile.NamedTemporaryFile()- cũng có thể được truy cập để đọc trong khi chúng được ghi vào subprocess.communicate.

Dưới đây là cách sử dụng mẫu:

        try:
            with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

Và đây là mã nguồn đã sẵn sàng để được sử dụng với nhiều bình luận nhất mà tôi có thể cung cấp để giải thích những gì nó làm:

Nếu bạn đang sử dụng python 2, hãy chắc chắn đầu tiên cài đặt phiên bản mới nhất của subprocess32 gói từ pypi.


import os
import sys
import threading
import time
import tempfile
import logging

if os.name == 'posix' and sys.version_info[0] < 3:
    # Support python 2
    import subprocess32 as subprocess
else:
    # Get latest and greatest from python 3
    import subprocess

logger = logging.getLogger(__name__)


class ProcessError(Exception):
    """Base exception for errors related to running the process"""


class ProcessTimeout(ProcessError):
    """Error that will be raised when the process execution will exceed a timeout"""


class ProcessRunner(object):
    def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs):
        """
        Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the
        Process Runner. This is a class that should be used as a context manager - and that provides an iterator
        for reading captured output from subprocess.communicate in near realtime.

        Example usage:


        try:
            with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner:
                for out in process_runner:
                    print(out)
        catch ProcessError as e:
            print(e.error_message)
            raise

        :param args: same as subprocess.Popen
        :param env: same as subprocess.Popen
        :param timeout: same as subprocess.communicate
        :param bufsize: same as subprocess.Popen
        :param seconds_to_wait: time to wait between each readline from the temporary file
        :param kwargs: same as subprocess.Popen
        """
        self._seconds_to_wait = seconds_to_wait
        self._process_has_timed_out = False
        self._timeout = timeout
        self._process_done = False
        self._std_file_handle = tempfile.NamedTemporaryFile()
        self._process = subprocess.Popen(args, env=env, bufsize=bufsize,
                                         stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs)
        self._thread = threading.Thread(target=self._run_process)
        self._thread.daemon = True

    def __enter__(self):
        self._thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._thread.join()
        self._std_file_handle.close()

    def __iter__(self):
        # read all output from stdout file that subprocess.communicate fills
        with open(self._std_file_handle.name, 'r') as stdout:
            # while process is alive, keep reading data
            while not self._process_done:
                out = stdout.readline()
                out_without_trailing_whitespaces = out.rstrip()
                if out_without_trailing_whitespaces:
                    # yield stdout data without trailing \n
                    yield out_without_trailing_whitespaces
                else:
                    # if there is nothing to read, then please wait a tiny little bit
                    time.sleep(self._seconds_to_wait)

            # this is a hack: terraform seems to write to buffer after process has finished
            out = stdout.read()
            if out:
                yield out

        if self._process_has_timed_out:
            raise ProcessTimeout('Process has timed out')

        if self._process.returncode != 0:
            raise ProcessError('Process has failed')

    def _run_process(self):
        try:
            # Start gathering information (stdout and stderr) from the opened process
            self._process.communicate(timeout=self._timeout)
            # Graceful termination of the opened process
            self._process.terminate()
        except subprocess.TimeoutExpired:
            self._process_has_timed_out = True
            # Force termination of the opened process
            self._process.kill()

        self._process_done = True

    @property
    def return_code(self):
        return self._process.returncode



1

Đây là một lớp học mà tôi đang sử dụng trong một trong các dự án của mình. Nó chuyển hướng đầu ra của một quy trình con đến nhật ký. Lúc đầu, tôi đã cố gắng ghi đè phương thức ghi nhưng nó không hoạt động vì quy trình con sẽ không bao giờ gọi nó (chuyển hướng xảy ra ở cấp độ filedescriptor). Vì vậy, tôi đang sử dụng đường ống của riêng mình, tương tự như cách nó được thực hiện trong mô-đun quy trình con. Điều này có lợi thế là đóng gói tất cả logic ghi nhật ký / in trong bộ điều hợp và bạn có thể chỉ cần chuyển các thể hiện của bộ ghi vào Popen:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))

class LogAdapter(threading.Thread):

    def __init__(self, logname, level = logging.INFO):
        super().__init__()
        self.log = logging.getLogger(logname)
        self.readpipe, self.writepipe = os.pipe()

        logFunctions = {
            logging.DEBUG: self.log.debug,
            logging.INFO: self.log.info,
            logging.WARN: self.log.warn,
            logging.ERROR: self.log.warn,
        }

        try:
            self.logFunction = logFunctions[level]
        except KeyError:
            self.logFunction = self.log.info

    def fileno(self):
        #when fileno is called this indicates the subprocess is about to fork => start thread
        self.start()
        return self.writepipe

    def finished(self):
       """If the write-filedescriptor is not closed this thread will
       prevent the whole program from exiting. You can use this method
       to clean up after the subprocess has terminated."""
       os.close(self.writepipe)

    def run(self):
        inputFile = os.fdopen(self.readpipe)

        while True:
            line = inputFile.readline()

            if len(line) == 0:
                #no new data was added
                break

            self.logFunction(line.strip())

Nếu bạn không cần đăng nhập mà chỉ muốn sử dụng, print()rõ ràng bạn có thể xóa phần lớn mã và giữ cho lớp ngắn hơn. Bạn cũng có thể mở rộng nó bằng một __enter____exit__phương pháp và kêu gọi finishedtrong __exit__để bạn có thể dễ dàng sử dụng nó như là bối cảnh.


1

Không có giải pháp Pythonic nào làm việc cho tôi. Hóa ra proc.stdout.read()hoặc tương tự có thể chặn mãi mãi.

Do đó, tôi sử dụng tee như thế này:

subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash')

Giải pháp này thuận tiện nếu bạn đang sử dụng shell=True.

${PIPESTATUS}nắm bắt trạng thái thành công của toàn bộ chuỗi lệnh (chỉ có sẵn trong Bash). Nếu tôi bỏ qua&& exit ${PIPESTATUS} , thì điều này sẽ luôn trả về 0 vì teekhông bao giờ thất bại.

unbuffercó thể cần thiết để in từng dòng ngay lập tức vào thiết bị đầu cuối, thay vì chờ quá lâu cho đến khi "bộ đệm ống" được lấp đầy. Tuy nhiên, unbuffer nuốt trạng thái thoát của assert (SIG Abort) ...

2>&1 cũng ghi nhật ký stderror vào tập tin.

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.