首页 > 解决方案 > 循环标准输出时轮询子进程完成

问题描述

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

这是代码:

#!/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是有道理的,因为在echosleeps 1 秒之后,才移动到下一次迭代并完成。

有什么办法让它工作吗?

标签: pythonpython-3.xsubprocess

解决方案


您已经确定了问题所在,也就是说,在子进程输出最后一行之后,它仍将继续运行一秒钟,因此当程序处于循环中时,将始终可以看到该程序正在运行。即使您将调用移到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调用通常是浪费的。您应该一直阅读,直到没有更多输出,然后执行 ap.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()

推荐阅读