轮询子进程在循环 stdout 时完成

Poll subprocess finished while looping stdout

我正在编写一个生成大小不可预测的输出的脚本,我想在脚本完成时从循环内部知道。

这是代码:

#!/usr/bin/env python3
import subprocess
import shlex

def main():
    cmd = 'bash -c "for i in $(seq 1 15);do echo $i ;sleep 1;done"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE,
                         universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}")
        print(p.poll())

if __name__ == "__main__":
    main()

即使在上一次迭代中,p.poll() 也始终是 None,这是有道理的,因为在 echo 之后,它 sleeps 持续 1 秒,然后才移动到下一个迭代并完成。

有什么办法让它起作用吗?

你已经确定了问题所在,即子进程在输出最后一行后仍会继续运行一秒钟,因此当程序在循环中时,程序将始终被视为运行ning。即使您将调用移到循环外的 poll ,您也可能需要稍等片刻,让子进程有机会在输出其最终消息后终止(我已经减小了循环大小——生命太短暂):

#!/usr/bin/env python3
import subprocess
import shlex
import time

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}", flush=True)
    print(p.poll())
    time.sleep(.1)
    print(p.poll())

if __name__ == "__main__":
    main()

打印:

bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"
file_name: 1
file_name: 2
file_name: 3
file_name: 4
file_name: 5
None
0

要在循环内“让它工作”需要对子进程内发生的事情有特殊的了解。根据上一段代码,我们需要:

#!/usr/bin/env python3
import subprocess
import shlex
import time

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        # has to be greater than the sleep time in the subprocess to give the subprocess a chance to terminate
        print(f"file_name: {line.strip()}", flush=True)
        time.sleep(1.1)
        print(p.poll())

if __name__ == "__main__":
    main()

打印:

bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"
file_name: 1
None
file_name: 2
None
file_name: 3
None
file_name: 4
None
file_name: 5
0

但这不是一个切实可行的解决方案。人们将不得不问进行此轮询的原因是什么?它不提供任何有用的信息,除非您愿意在读取后包含 sleep 调用 ,因为在子进程完成最后一次写入及其终止后总会有一些延迟 ,并且这些 sleep 调用通常是浪费的。您应该一直阅读直到没有更多输出,然后执行 p.wait() 以等待子进程终止,但这是您的选择:

#!/usr/bin/env python3
import subprocess
import shlex

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}", flush=True)
    p.wait()

if __name__ == "__main__":
    main()