Python子进程:"write error: Broken pipe"

19

我有一个管道问题,无法正确运行subprocess.Popen。

代码:

import subprocess
cmd = 'cat file | sort -g -k3 | head -20 | cut -f2,3' % (pattern,file)
p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
for line in p.stdout:
    print(line.decode().strip())

文件长度约为1000行的输出结果:

...
sort: write failed: standard output: Broken pipe
sort: write error

文件长度> 241行的输出:

...
sort: fflush failed: standard output: Broken pipe
sort: write error

文件长度小于241行时输出正常。

我一直在阅读文档并疯狂地搜索,但我似乎漏掉了关于子进程模块的一些基本内容...可能与缓冲区有关。我试过使用p.stdout.flush()和更改缓冲区大小以及p.wait()。我尝试用类似'sleep 20; cat moderatefile'的命令来重现此问题,但这似乎在没有错误的情况下运行。


1
“New code” 非常有用。我喜欢在测试 shell 中使用的完全相同的管道命令。两个建议:1)复数形式:run_shell_commands 2)要么删除、注释或在函数内的打印语句周围添加 debug=false。 - PeterVermont
1
谢谢。遇到了相同的管道破裂问题,对于超过一定大小的文件。使用了您的代码,它完美地解决了这个问题。 - poof
顺便说一下,如果任何一个命令在 stderr 上产生足够的输出,代码可能会死锁。您应该在将它传递给Popen后在父进程中关闭“stdout_old”,以允许SIGPIPE上游(它应该杀死“sort”而不是生成EPIPE)。参见'yes' reporting error with subprocess communicate() - jfs
你能在当前的Python版本2.7和3.3上重现这个错误吗? - jfs
相关内容:替换shell管道 - jfs
显示剩余2条评论
5个回答

15

以下内容摘自subprocess文档的实例:

# To replace shell pipeline like output=`dmesg | grep hda`
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

1
Shell并不是问题的原因,但是以某种方式在“正确”的位置分割命令似乎可以解决它。谢谢! - mathtick
@mathtick:你确实应该遍历PIPE而不是将大量输出归因于某个字符串实例,否则你会冒出内存溢出的风险。 - Paulo Scardine

5
这是因为在传递给subprocess.Popen的命令中不应使用“shell管道”,而应该像这样使用subprocess.PIPE
from subprocess import Popen, PIPE

p1 = Popen('cat file', stdout=PIPE)
p2 = Popen('sort -g -k 3', stdin=p1.stdout, stdout=PIPE)
p3 = Popen('head -20', stdin=p2.stdout, stdout=PIPE)
p4 = Popen('cut -f2,3', stdin=p3.stdout)
final_output = p4.stdout.read()

但我必须说,你试图做的事情可以在纯Python中完成,而不是调用一堆shell命令。


4
我正在对超过1300万行进行grep匹配,返回了超过10万行的匹配结果,并进行排序、切割和取“头部”操作。这在shell中只需要几秒钟就能完成。但是用Python做同样的操作却需要很长时间。我尝试了read()方法以及分割命令,但好像问题依旧存在。测试后会再次回复。 - mathtick
1
分割命令似乎已经解决了问题,即使我仍然使用shell=True。 - mathtick

1
我一直遇到同样的错误。甚至将管道放在bash脚本中执行,而不是在Python中执行。从Python中,它会得到破损的管道错误,而从bash中则不会。
在我的看来,可能是在head之前的最后一个命令抛出了错误,因为它(排序)的STDOUT已关闭。Python必须注意到这一点,而对于shell来说,错误是静默的。我已经改变了我的代码,消耗整个输入,错误就消失了。
当文件较小时,这也可以解释为什么管道可以正常工作,因为管道在head退出之前可能会缓冲整个输出。这就解释了在大文件上出现的错误。
例如,我用awk 'NR == 1'代替了'head -1'(在我的情况下,我只需要第一行)。
根据管道中“head -X”的位置,可能有更好的方法来完成此操作。

0

您不需要shell=True。不要调用Shell。这是我会做的方式:

p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout_value = p.communicate()[0] 
stdout_value   # the output

使用后是否遇到了缓冲区问题?


外壳似乎并没有引起问题。在正确的位置分割命令似乎已经解决了它(请参见更新)。谢谢! - mathtick

0

尝试使用communicate(),而不是直接从stdout读取。

Python文档中说:

"警告:使用communicate()而不是.stdin.write、.stdout.read或.stderr.read,以避免由于任何其他操作系统管道缓冲区填满并阻塞子进程而导致死锁。"

http://docs.python.org/library/subprocess.html#subprocess.Popen.stdout

p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
output =  p.communicate[0]
for line in output:
    # do stuff

我尝试了p.communicate()[0],但这并没有解决问题。适当地分割命令确实解决了问题(请参见上文)。但我仍然不太明白为什么会起作用。 - mathtick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接