Python subprocess.Popen 在使用 shell 和 pipe 时阻塞

2 投票
1 回答
582 浏览
提问于 2025-04-18 02:42

运行以下命令:yes | head -1,在命令行中会输出一个y。但是,如果用Python的subprocess模块来调用这个命令,程序就会一直卡在那里,不会继续执行:

import subprocess

arg = "yes | head -1"
process = subprocess.Popen(arg,
    shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)

print "Command started: %d" % process.pid
r = process.communicate()
print "Command ended: %s %s" % r

即使用kill命令强制结束这个进程也没有用,或者尝试通过preexec_fn=os.setsid让子进程成为它的会话领导者也不行。

这是什么原因造成的呢?有没有办法能解决这个问题?

我使用的是Python 2.7.3,/bin/shGNU bash, version 3.2.48(1)-release (x86_64-apple-darwin12)

1 个回答

1

原来这是因为 Python 在 subprocess 模块中没有在执行之前重置信号处理程序,这个问题是个已知的bug。

这个问题和 'yes' 在 subprocess communicate() 中报错 的情况是一样的。不同的是,GNU 的 yes 在写入时遇到 EPIPE 错误会导致程序直接崩溃,而 BSD 的 yes(在我看来,OS X 上用的就是这个)在写入时不会检查返回代码,所以如果没有 SIGPIPE 信号,yes 就不会停止。

这个问题可以通过将 reset_signals 的代码移植过来解决,具体方法可以参考上面提到的问题:

def restore_signals():
    signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ')
    for sig in signals:
        if hasattr(signal, sig):
            signal.signal(getattr(signal, sig), signal.SIG_DFL)

然后在 Popen 调用中设置 preexec_fn=restore_signals

撰写回答