A non-blocking read on a subprocess.PIPE in Python

后端 未结 29 2569
醉酒成梦
醉酒成梦 2020-11-21 04:49

I\'m using the subprocess module to start a subprocess and connect to its output stream (standard output). I want to be able to execute non-blocking reads on its standard ou

29条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-11-21 04:58

    Here is a simple solution based on threads which:

    • works on both Linux and Windows (not relying on select).
    • reads both stdout and stderr asynchronouly.
    • doesn't rely on active polling with arbitrary waiting time (CPU friendly).
    • doesn't use asyncio (which may conflict with other libraries).
    • runs until the child process terminates.

    printer.py

    import time
    import sys
    
    sys.stdout.write("Hello\n")
    sys.stdout.flush()
    time.sleep(1)
    sys.stdout.write("World!\n")
    sys.stdout.flush()
    time.sleep(1)
    sys.stderr.write("That's an error\n")
    sys.stderr.flush()
    time.sleep(2)
    sys.stdout.write("Actually, I'm fine\n")
    sys.stdout.flush()
    time.sleep(1)
    

    reader.py

    import queue
    import subprocess
    import sys
    import threading
    
    
    def enqueue_stream(stream, queue, type):
        for line in iter(stream.readline, b''):
            queue.put(str(type) + line.decode('utf-8'))
        stream.close()
    
    
    def enqueue_process(process, queue):
        process.wait()
        queue.put('x')
    
    
    p = subprocess.Popen('python printer.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    q = queue.Queue()
    to = threading.Thread(target=enqueue_stream, args=(p.stdout, q, 1))
    te = threading.Thread(target=enqueue_stream, args=(p.stderr, q, 2))
    tp = threading.Thread(target=enqueue_process, args=(p, q))
    te.start()
    to.start()
    tp.start()
    
    while True:
        line = q.get()
        if line[0] == 'x':
            break
        if line[0] == '2':  # stderr
            sys.stdout.write("\033[0;31m")  # ANSI red color
        sys.stdout.write(line[1:])
        if line[0] == '2':
            sys.stdout.write("\033[0m")  # reset ANSI code
        sys.stdout.flush()
    
    tp.join()
    to.join()
    te.join()
    

提交回复
热议问题