考虑代码:
printf '%s\n' 1 2 3 4 5 | head -n 2
其输出为:
1
2
我的理解是,当head
进程在读取前两行后破坏管道时,该printf
进程会捕获损坏的管道并优雅退出。
在我的一个脚本中,我使用一个 python 应用程序,它不会在损坏的管道上正常退出,而是继续运行,直到它终止完成它设定的操作或由于某些其他原因终止。每次它尝试写入标准输出并失败时,它都会抱怨broken pipe
.
本文介绍了如何在 python 中处理损坏的管道。
我也许可以鼓励应用程序开发人员实现对破损管道的处理。出于某些原因,我怀疑他们会这样做。我也许可以分叉该应用程序,但它相当复杂,所以不愿意。
我似乎剩下的唯一选择是试图找到一种方法,让 shell (Bash) 在尝试写入管道时杀死进程。这可能吗?如果是这样,我该怎么做?
是的,python3 有一种令人讨厌的行为,它会忽略 SIGPIPE,并在
write()
EPIPE 失败时引发异常,即使损坏的管道是生活和正常操作的一部分。您确实可以通过使用一个包装器来解决它,该包装器转发其输出并使用 SIGTERM 杀死 python3,例如当输出变成损坏的管道时。
bash
不过,这不是我用来做这种事情的外壳。你可以使用
perl
:如果必须是 shell,zsh 会是更好的选择:
例子:
现在,让一个进程花费时间通过额外的管道推送输出,只是为了解决 python3 的烦恼,似乎有点过分了。
另一种方法可能是
python3
从一开始就防止忽略这些信号。有效地
strace
短路了 python3 的所有rt_sigaction()
系统调用调用,防止它安装任何信号处理程序或更改(任何信号的)信号处理。因此,它还消除了您在停止 python3 脚本时收到的烦人消息
^C
,但如果您的 python 脚本确实安装了一些信号处理程序以在被杀死时自行清理,则可能会很危险。最好只对有关 SIGPIPE 信号的调用执行此操作,但据我所知,strace 无法执行此操作。
$LD_PRELOAD
不过,您应该能够使用一些技巧来实现它:python3
就像行为良好的可执行文件一样,被 SIGPIPE 悄悄杀死。无论
strace
有没有该-f
选项,sigaction()
调用都只会被父进程(执行的进程python3
)拦截。即使它执行单独的命令,也会继续拦截它们,但不会在子命令中执行此操作(除非您传递该-f
选项)。虽然该LD_PRELOAD
技巧将影响所有子进程,甚至在它们执行单独的命令(如果这些命令是动态链接的)之后也是如此。与 python3 编程语言(大多数人使用的一种)的 cpython 解释器配合使用的一种更简洁的方法是在自定义中恢复 SIGPIPE 的默认信号配置
sitecustomize.py
:在这里,我们不更改系统的目录
sitecusomize.py
,而是在专用目录中更改一个PYTHONPATH
目录,因此我们可以仅针对那些我们希望在尝试写入损坏的管道时被 SIGPIPE 杀死的 python3 脚本将变量设置为该目录。那个似乎不适用于
pypy3
.