อ่านกระบวนการ stdout ย่อยโดยบรรทัด


235

สคริปต์ python ของฉันใช้กระบวนการย่อยเพื่อเรียกใช้ยูทิลิตี linux ที่มีเสียงดังมาก ฉันต้องการเก็บผลลัพธ์ทั้งหมดไปยังล็อกไฟล์และแสดงบางส่วนแก่ผู้ใช้ ฉันคิดว่าสิ่งต่อไปนี้ใช้งานได้ แต่เอาต์พุตไม่ปรากฏขึ้นในแอปพลิเคชันของฉันจนกว่ายูทิลิตี้จะสร้างเอาต์พุตจำนวนมาก

#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()

พฤติกรรมที่ฉันต้องการจริงๆคือสคริปต์ตัวกรองเพื่อพิมพ์แต่ละบรรทัดตามที่ได้รับจากกระบวนการย่อย Sorta ชอบอะไรteeแต่ด้วยรหัสหลาม

ฉันพลาดอะไรไป เป็นไปได้ไหม


ปรับปรุง:

หาก a sys.stdout.flush()ถูกเพิ่มเข้าไปใน fake_utility.py รหัสจะมีพฤติกรรมที่ต้องการใน python 3.1 ฉันใช้ไพ ธ อน 2.6 คุณอาจคิดว่าการใช้proc.stdout.xreadlines()งานจะเหมือนกับ py3k แต่ก็ไม่เป็นเช่นนั้น


อัปเดต 2:

นี่คือรหัสการทำงานที่น้อยที่สุด

#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
คุณสามารถใช้print line,แทนprint line.rstrip()(หมายเหตุ: เครื่องหมายจุลภาคในตอนท้าย)
jfs


2
อัพเดต 2 ระบุว่าใช้งานได้กับ python 3.0+ แต่ใช้คำสั่งการพิมพ์เก่าดังนั้นจึงไม่สามารถทำงานกับ python 3.0+ ได้
Rooky

ไม่มีคำตอบที่ระบุไว้ที่นี่ทำงานได้สำหรับฉัน แต่stackoverflow.com/questions/5411780/…ไม่ได้!
บรรจุกล่อง

คำตอบ:


179

เป็นเวลานานแล้วที่ฉันได้ทำงานกับ Python ครั้งล่าสุด แต่ฉันคิดว่าปัญหานี้เกิดจากคำสั่งfor line in proc.stdoutซึ่งอ่านอินพุตทั้งหมดก่อนที่จะวนซ้ำ วิธีแก้ไขคือใช้readline()แทน:

#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()

แน่นอนคุณยังต้องจัดการกับบัฟเฟอร์ของกระบวนการย่อย

หมายเหตุ: ตามเอกสารอธิบายโซลูชันที่มีตัววนซ้ำควรเทียบเท่ากับการใช้readline()ยกเว้นบัฟเฟอร์ read-ahead แต่ (หรือเพราะเหตุนี้) การเปลี่ยนแปลงที่เสนอทำให้ผลลัพธ์ที่แตกต่างสำหรับฉัน (Python 2.5 บน Windows XP)


11
สำหรับfile.readline()vs. for line in fileดูbugs.python.org/issue3907 (โดยย่อ: ใช้งานได้กับ Python3; ใช้io.open()กับ Python 2.6+)
jfs

5
การทดสอบแบบ pythonic เพิ่มเติมสำหรับ EOF ตาม "คำแนะนำการเขียนโปรแกรม" ใน PEP 8 ( python.org/dev/peps/pep-0008 ) จะเป็น 'ถ้าไม่ใช่บรรทัด:'
Jason Mock

14
@naxa: for line in iter(proc.stdout.readline, ''):สำหรับท่อ:
jfs

3
@ Jan-PhilipGehrcke: ใช่ 1. คุณสามารถใช้for line in proc.stdoutกับ Python 3 (ไม่มีข้อผิดพลาดในการอ่านล่วงหน้า) 2. '' != b''สำหรับ Python 3 - อย่าคัดลอกและวางโค้ดสุ่มสี่สุ่มห้า - คิดว่ามันทำงานอย่างไร
jfs

2
@JFSebastian: แน่นอนว่าการiter(f.readline, b'')แก้ปัญหาค่อนข้างชัดเจน (และยังสามารถใช้งานได้กับ Python 2 หากใครสนใจ) ประเด็นของความคิดเห็นของฉันไม่ได้เป็นการตำหนิการแก้ปัญหาของคุณ (ขออภัยถ้ามันปรากฏเช่นนั้นฉันอ่านมันตอนนี้ด้วย!) แต่เพื่ออธิบายขอบเขตของอาการซึ่งค่อนข้างรุนแรงในกรณีนี้ (ส่วนใหญ่ของ Py2 / 3 ปัญหาส่งผลให้มีข้อยกเว้นในขณะที่นี่วนรอบตัวที่ประพฤติดีเปลี่ยนเป็นไม่มีที่สิ้นสุดและการรวบรวมขยะต่อสู้กับน้ำท่วมของวัตถุที่สร้างขึ้นใหม่ให้ผลการใช้งานหน่วยความจำสั่นด้วยระยะเวลานานและขนาดใหญ่)
ดร. Jan-Philip Gehrcke

45

สายไปงานปาร์ตี้ แต่ก็แปลกใจที่ไม่เห็นสิ่งที่ฉันคิดว่าเป็นทางออกที่ง่ายที่สุดที่นี่:

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

(ต้องใช้ Python 3)


25
ฉันต้องการใช้คำตอบนี้ แต่ฉันได้รับ: AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
ทำงานร่วมกับหลาม 3
matanster

เห็นได้ชัดว่ารหัสนี้ไม่ถูกต้องด้วยเหตุผลหลายประการที่เข้ากันได้กับ py3 / py3 และความเสี่ยงที่แท้จริงของการได้รับ ValueError: การดำเนินการ I / O ในไฟล์ปิด
sorin

3
@ โซรินไม่ได้ทำให้สิ่งเหล่านั้น "ไม่ถูกต้อง" หากคุณกำลังเขียนไลบรารีที่ยังต้องการรองรับ Python 2 อย่าใช้รหัสนี้ แต่หลายคนมีความหรูหราของความสามารถในการใช้ซอฟต์แวร์ที่เปิดตัวเมื่อเร็ว ๆ นี้กว่าทศวรรษ หากคุณพยายามอ่านไฟล์ปิดคุณจะได้รับข้อยกเว้นนั้นไม่ว่าคุณจะใช้TextIOWrapperหรือไม่ก็ตาม คุณสามารถจัดการข้อยกเว้นได้
jbg

1
คุณอาจมาสายไปงานปาร์ตี้ แต่คำตอบของคุณเป็นรุ่นล่าสุดกับ Python เวอร์ชันปัจจุบัน, ty
Dusan Gligoric

20

แน่นอนถ้าคุณแยกตัววนซ้ำแล้วการบัฟเฟอร์อาจเป็นปัญหาของคุณได้ คุณสามารถบอก python ในกระบวนการย่อยไม่ให้บัฟเฟอร์เอาต์พุต

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

กลายเป็น

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

ฉันต้องการสิ่งนี้เมื่อเรียกหลามจากภายในหลาม


14

คุณต้องการส่งพารามิเตอร์พิเศษเหล่านี้ไปที่subprocess.Popen:

bufsize=1, universal_newlines=True

จากนั้นคุณสามารถทำซ้ำในตัวอย่างของคุณ (ทดสอบกับ Python 3.5)


2
@nicoulaj มันควรจะทำงานถ้าใช้แพ็คเกจย่อยประมวลผล 32
Quantum7

4

ฟังก์ชั่นที่ช่วยให้ iterating ทั้งสองstdoutและstderrเห็นพ้องกันในเรียลไทม์ทีละบรรทัด

ในกรณีที่คุณต้องการรับกระแสเอาต์พุตสำหรับทั้งสองstdoutและstderrในเวลาเดียวกันคุณสามารถใช้ฟังก์ชันต่อไปนี้

ฟังก์ชันใช้ Queues เพื่อผสานทั้ง Popen pipes เข้ากับ iterator เดียว

ที่นี่เราสร้างฟังก์ชั่น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() ในการใช้งาน:

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

คุณยังสามารถอ่านบรรทัดที่ไม่มีลูป ทำงานใน python3.6

import os
import subprocess

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

1
หรือแปลงเป็นสตริง:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

ฉันพยายามนี้กับ python3 และการทำงาน, แหล่งที่มา

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

การแก้ไขคำตอบของRômuloต่อไปนี้ใช้ได้กับฉันใน Python 2 และ 3 (2.7.12 และ 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

Dunno เมื่อสิ่งนี้ถูกเพิ่มลงในโมดูลย่อย แต่ด้วย Python 3 คุณควรใช้งานได้ดีproc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.