使用subprocess communicate()时报告'yes'错误

4 投票
3 回答
2990 浏览
提问于 2025-04-17 20:20

我在用下面这个函数在Python里运行命令:

def run_proc(cmd):
    child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = child.communicate()
    returncode = child.returncode
    return stdout, stderr, returncode

这个函数一直都能正常工作,不过现在我想用yes这个程序来把输出传给标准输入(stdin)。我想运行的命令是:

yes '' | apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade

但我觉得可以用一个更通用的例子来替代,比如:

yes | head -3 | cat

我的问题是,如果我尝试运行任何包含yes |的命令,上面的subprocess.Popen就会出现错误信息:

yes: standard output: Broken pipe
yes: write error

对我来说,似乎这个管道还是能正常工作的,因为从yes | head -3 | cat的结果来看,输出是y y y

我有以下几个问题:

  1. 即使yes报告了错误,管道功能还是有效的吗?
  2. 我该怎么解决这个问题?

3 个回答

9

这个问题是因为在Python 3.2之前,subprocess模块没有把SIGPIPE信号的处理方式恢复到默认状态。这就是为什么你会看到EPIPE写入错误的原因。

在Python 3.2及之后的版本中:

>>> from subprocess import check_output
>>> check_output("yes | head -3", shell=True)
b'y\ny\ny\n'

head退出时,yes会被SIGPIPE信号杀掉。

在Python 2中:

>>> from subprocess import check_output
>>> check_output("yes | head -3", shell=True)
yes: standard output: Broken pipe
yes: write error
'y\ny\ny\n'

yes会出现EPIPE写入错误。这个错误可以安全地忽略。它传达的信息和SIGPIPE是一样的

为了绕过这个问题,你可以在Python 2中使用preexec_fn参数来模拟restore_signals

>>> from subprocess import check_output
>>> import signal
>>> def restore_signals(): # from http://hg.python.org/cpython/rev/768722b2ae0a/
...     signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ')
...     for sig in signals:
...         if hasattr(signal, sig):
...            signal.signal(getattr(signal, sig), signal.SIG_DFL)
... 
>>> check_output("yes | head -3", shell=True, preexec_fn=restore_signals)
'y\ny\ny\n'

撰写回答