đọc stdout dòng quy trình con theo dòng


235

Kịch bản python của tôi sử dụng quy trình con để gọi một tiện ích linux rất ồn. Tôi muốn lưu trữ tất cả các đầu ra vào một tệp nhật ký và hiển thị một số tệp đó cho người dùng. Tôi nghĩ rằng những điều sau đây sẽ hoạt động, nhưng đầu ra không hiển thị trong ứng dụng của tôi cho đến khi tiện ích tạo ra một lượng đầu ra đáng kể.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Hành vi tôi thực sự muốn là cho tập lệnh bộ lọc in từng dòng khi nó được nhận từ quy trình con. Sắp xếp như những gì teelàm nhưng với mã python.

Tôi đang thiếu gì? Điều này thậm chí có thể?


Cập nhật:

Nếu a sys.stdout.flush()được thêm vào fake_utility.py, mã có hành vi mong muốn trong python 3.1. Tôi đang sử dụng python 2.6. Bạn sẽ nghĩ rằng việc sử dụng proc.stdout.xreadlines()sẽ hoạt động giống như py3k, nhưng không.


Cập nhật 2:

Đây là mã làm việc tối thiểu.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
bạn có thể sử dụng print line,thay vì print line.rstrip()(lưu ý: dấu phẩy ở cuối).
jfs


2
Cập nhật 2 trạng thái rằng nó hoạt động với python 3.0+ nhưng sử dụng câu lệnh in cũ, vì vậy nó không hoạt động với python 3.0+.
Rooky

Không có câu trả lời nào được liệt kê ở đây có hiệu quả với tôi, nhưng stackoverflow.com/questions/5411780/ đã làm!
đóng hộp

Câu trả lời:


179

Đã lâu rồi kể từ lần cuối tôi làm việc với Python, nhưng tôi nghĩ vấn đề là do câu lệnh for line in proc.stdout, nó đọc toàn bộ đầu vào trước khi lặp lại nó. Giải pháp là sử dụng readline()thay thế:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

Tất nhiên bạn vẫn phải đối phó với bộ đệm của quy trình con.

Lưu ý: theo tài liệu , giải pháp với iterator phải tương đương với việc sử dụng readline(), ngoại trừ bộ đệm đọc trước, nhưng (hoặc chính xác là vì điều này) thay đổi được đề xuất đã tạo ra kết quả khác nhau cho tôi (Python 2.5 trên Windows XP).


11
cho file.readline()vs for line in filethấy bugs.python.org/issue3907 (trong ngắn hạn: nó hoạt động trên Python3; sử dụng io.open()trên Python 2.6+)
JFS

5
Thử nghiệm pythonic nhiều hơn cho EOF, theo "Khuyến nghị lập trình" trong PEP 8 ( python.org/dev/peps/pep-0008 ), sẽ là 'nếu không phải là dòng:'.
Jason Mock

14
@naxa: cho đường ống : for line in iter(proc.stdout.readline, ''):.
jfs

3
@ Jan-PhilipGehrcke: có. 1. bạn có thể sử dụng for line in proc.stdouttrên Python 3 (không có lỗi đọc trước) 2. '' != b''trên Python 3 - không sao chép-dán mã một cách mù quáng - hãy nghĩ nó làm gì và hoạt động như thế nào.
jfs

2
@JFSebastian: chắc chắn, iter(f.readline, b'')giải pháp khá rõ ràng (và cũng hoạt động trên Python 2, nếu có ai quan tâm). Quan điểm của tôi không phải là đổ lỗi cho giải pháp của bạn 3 vấn đề dẫn đến ngoại lệ, trong khi ở đây, một vòng lặp hoạt động tốt đã biến thành vô tận, và bộ sưu tập rác đấu tranh chống lại lũ lụt của các vật thể mới được tạo ra, mang lại dao động sử dụng bộ nhớ với thời gian dài và biên độ lớn).
Tiến sĩ Jan-Philip Gehrcke 23/2/2015

45

Bit đến bữa tiệc muộn, nhưng thật ngạc nhiên khi không thấy những gì tôi nghĩ là giải pháp đơn giản nhất ở đây:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(Điều này yêu cầu Python 3.)


25
Tôi muốn sử dụng câu trả lời này nhưng tôi nhận được: AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
Hoạt động với python 3
matanster

Rõ ràng mã này không hợp lệ vì nhiều lý do tương thích py3 / py3 và rủi ro thực sự khi nhận ValueError: Thao tác I / O trên tệp đã đóng
sorin

3
@sorin không phải những điều đó làm cho nó "không hợp lệ". Nếu bạn đang viết thư viện vẫn cần hỗ trợ Python 2, thì đừng sử dụng mã này. Nhưng nhiều người có thể sử dụng phần mềm được phát hành gần đây hơn một thập kỷ trước. Nếu bạn cố đọc trên một tệp đã đóng, bạn sẽ nhận được ngoại lệ đó bất kể bạn có sử dụng TextIOWrapperhay không. Bạn chỉ có thể xử lý ngoại lệ.
jbg

1
bạn có thể đến bữa tiệc muộn nhưng bạn trả lời đã cập nhật với phiên bản hiện tại của Python, ty
Dusan Gligoric

20

Thật vậy, nếu bạn đã sắp xếp trình vòng lặp thì bộ đệm bây giờ có thể là vấn đề của bạn. Bạn có thể nói với con trăn trong quy trình con không đệm đầu ra của nó.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

trở thành

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

Tôi đã cần điều này khi gọi python từ bên trong python.


14

Bạn muốn truyền các tham số phụ này cho subprocess.Popen:

bufsize=1, universal_newlines=True

Sau đó, bạn có thể lặp lại như trong ví dụ của bạn. (Đã thử nghiệm với Python 3.5)


2
@nicoulaj Nó sẽ hoạt động nếu sử dụng gói sub process32.
Quantum7

4

Một hàm cho phép lặp lại cả hai stdoutstderrđồng thời, trong thời gian thực, từng dòng một

Trong trường hợp bạn cần lấy luồng đầu ra cho cả hai stdoutstderrcùng một lúc, bạn có thể sử dụng chức năng sau.

Hàm sử dụng Hàng đợi để hợp nhất cả hai ống Popen vào một trình vòng lặp duy nhất.

Ở đây chúng ta tạo hàm read_popen_pipes():

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()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() đang sử dụng:

import subprocess as sp


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):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

Bạn cũng có thể đọc các dòng w / o loop. Hoạt động trong python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
Hoặc để chuyển đổi thành chuỗi:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

Tôi đã thử điều này với python3 và nó đã hoạt động, nguồn

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

Sửa đổi sau đây về câu trả lời của Rômulo hoạt động với tôi trên Python 2 và 3 (2.7.12 và 3.6.1):

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

Không biết điều này đã được thêm vào mô đun quy trình con, nhưng với Python 3, bạn sẽ ổn khi sử dụng proc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
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.