subprocess collect output while external program is running


For this to work properly the external program might need to set the output to unbuffered. In Python by default prining to STDERR is unbuffered, but we had to pass flush=True to the print function to make it unbuffered for STDOUT as well.

examples/process/run_command_collect_while_running.py
import subprocess
import sys
import time

def run_process(command, timeout):
    print("Before Popen")
    proc = subprocess.Popen(command,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE,
        universal_newlines=True,
        bufsize=0,
    )
    print("After Popen")
    out = ""
    err = ""

    while True:
        exit_code = proc.poll()
        print(f"poll: {exit_code} {time.time()}")
        this_out = proc.stdout.readline()
        this_err = proc.stderr.readline()
        print(f"out: {this_out}", end="")
        print(f"err: {this_err}", end="")
        out += this_out
        err += this_err
        time.sleep(0.5)  # here we could actually do something useful
        timeout -= 0.5
        if timeout <= 0:
            break
        if exit_code is not None:
            break

    print(f"Final: {exit_code}")
    if exit_code is None:
        raise Exception("Timeout")

    return exit_code, out, err

exit_code, out, err = run_process([sys.executable, 'process.py', '4', '3'], 20)
#exit_code, out, err = run_process(['docker-compose', 'up', '-d'], 20)

print("-----")
print(f"exit_code: {exit_code}")
print("OUT")
print(out)
print("ERR")
print(err)

Before Popen
After Popen
poll: None 1637589106.083494
out: OUT 0
err: ERR 0
poll: None 1637589106.6035957
out: OUT 1
err: ERR 1
poll: None 1637589107.6047328
out: OUT 2
err: ERR 2
poll: None 1637589108.6051855
out: OUT 3
err: ERR 3
poll: None 1637589109.6066446
out: err: poll: 0 1637589110.6227856
out: err: Final: 0
-----
exit_code: 0
OUT

ERR