将 stdin 通过管道传输到函数时,分段错误输出会被抑制。为什么?

将 stdin 通过管道传输到函数时,分段错误输出会被抑制。为什么?

让我们定义一个函数来执行二进制文件:

function execute() { ./binary; }

然后定义第二个函数,将文本文件通过管道传输到第一个函数中:

function test() { cat in.txt | execute; }

如果binary因段错误而崩溃,则从testCLI 调用将导致139返回代码,但错误“段错误”不会打印到终端。

如果我们定义直接test调用,则会打印“分段错误” :binary

function test() { cat in.txt | ./binary; }

如果我们定义调用execute而不将 stdin 输入到其中,它也会被打印:

function test() { execute; }

in.txt最后,如果我们直接重定向execute而不是通过管道,它也会被打印:

function test() { execute <in.txt; }

这是在 Bash 4.4 上测试的。这是为什么?

答案1

该诊断消息由交互式 shell 生成作业控制系统,为了用户的利益 - 它不是来自崩溃的底层程序。当您通过管道输入 shell 函数时子外壳生成来运行该函数,并且该子 shell 不被视为面向用户。如果正常调用该函数,它将在原始 shell 中运行,并打印消息。

您可以通过在当前 shell 中禁用作业控制来测试这一点

set +m

然后./binary再次运行:现在它也不会在那里打印任何内容。使用 重新启用作业控制set -m

即使是裸露的子外壳也具有相同的效果:

( : ; ./binary )

将不打印任何诊断信息(其中需要两个命令以避免子 shell 删除优化)。管道出去该函数的作用也是如此。

作业控制在子 shell 中被禁用,即使手动启用它,它也会被静音。这是系统中的一个不幸的缺陷。在非交互式 shell 中,消息总是通过不同的机制报告,在交互式 shell 中的任何其他地方也如此。


如果打印诊断对您来说很重要,那么制作脚本而不是函数将允许您确保它始终包含在内。由于您在管道中使用该函数,因此无论如何您都无法执行任何需要函数的操作,因此这样做不会产生重大成本。


我不会说这是一个错误。以这种方式行事的一个可能原因是命令替换$(...),它也运行一个子 shell,行为适当:

foo=$(echo|test)

不应导致诊断消息存储在 中foo,以便管道故障导致空扩展。另一种方法是故意暂时抑制诊断消息。

相关内容