Python的subprocess.Popen对象在子进程未退出时收集输出时挂起

2 投票
4 回答
6401 浏览
提问于 2025-04-15 18:34

当一个程序异常退出或者根本没有退出时,我还是想收集它在那之前可能产生的输出。

这个例子中显而易见的解决办法是用os.kill来结束子进程,但在我的实际代码中,子进程卡住了,正在等待NFS(网络文件系统),对SIGKILL信号没有反应。

#!/usr/bin/python
import subprocess
import os
import time
import signal
import sys
child_script = """
#!/bin/bash
i=0
while [ 1 ]; do
    echo "output line $i"
    i=$(expr $i \+ 1)
    sleep 1
done
"""
childFile = open("/tmp/childProc.sh", 'w')
childFile.write(child_script)
childFile.close()

cmd = ["bash", "/tmp/childProc.sh"]
finish = time.time() + 3
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
while p.poll() is None:
    time.sleep(0.05)
    if finish < time.time():
        print "timed out and killed child, collecting what output exists so far"
        out, err = p.communicate()
        print "got it"
        sys.exit(0)

在这种情况下,关于超时的打印语句会出现,但这个Python脚本却永远不会退出或继续执行。有没有人知道我可以用什么其他方法来获取子进程的输出呢?

4 个回答

0

在另一个StackOverflow的问题中有一些不错的建议:我该如何从Python中的subprocess.Popen获取“实时”信息(2.5)

里面的大部分提示都是使用 pipe.readline(),而不是 pipe.communicate(),因为后者只会在进程结束时返回结果。

1

这里有一种POSIX的方法,可以在不使用临时文件的情况下完成这个任务。我知道在这里使用子进程有点多余,但因为原始问题中提到了它……

import subprocess
import os
import time
import signal
import sys

pr, pw = os.pipe()
pid = os.fork () 

if pid: #parent
    os.close(pw)
    cmd = ["bash"]
    finish = time.time() + 3
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=pr, close_fds=True)
    while p.poll() is None:
        time.sleep(0.05)
        if finish < time.time():
            os.kill(p.pid, signal.SIGTERM)
            print "timed out and killed child, collecting what output exists so far"
            out, err = p.communicate()
            print "got it: ", out
            sys.exit(0)

else: #child
    os.close(pr)
    child_script = """
    #!/bin/bash
    while [ 1 ]; do
        ((++i))
        echo "output line $i"
        sleep 1
    done
    """
    os.write(pw, child_script)
1

问题是,当bash没有连接到终端时,按CTRL-C不会有反应。

切换到SIGHUP或SIGTERM信号似乎可以解决这个问题:

cmd = ["bash", 'childProc.sh']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                          stderr=subprocess.STDOUT, 
                          close_fds=True)
time.sleep(3)
print 'killing pid', p.pid
os.kill(p.pid, signal.SIGTERM)
print "timed out and killed child, collecting what output exists so far"
out  = p.communicate()[0]
print "got it", out

输出结果:

killing pid 5844
timed out and killed child, collecting what output exists so far
got it output line 0
output line 1
output line 2

撰写回答