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

Dun*_*eal 6 command-line bash

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

function execute() { ./binary; }
Run Code Online (Sandbox Code Playgroud)

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

function test() { cat in.txt | execute; }
Run Code Online (Sandbox Code Playgroud)

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

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

function test() { cat in.txt | ./binary; }
Run Code Online (Sandbox Code Playgroud)

如果我们定义在execute不通过管道输入标准输入的情况下调用它,它也会被打印出来:

function test() { execute; }
Run Code Online (Sandbox Code Playgroud)

最后,它也得到如果我们重定向打印in.txtexecute通过管道,而不是直接的:

function test() { execute <in.txt; }
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*mer 7

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

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

set +m
Run Code Online (Sandbox Code Playgroud)

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

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

( : ; ./binary )
Run Code Online (Sandbox Code Playgroud)

将不打印诊断信息(在那里需要两个命令以避免子shell-eliding 优化)。管道出来的功能确实太。

在子外壳中禁用作业控制,即使手动启用它,它也会被静音。这是系统中令人遗憾的差距。在非交互式 shell 中,消息将始终通过不同的机制报告,并且在交互式 shell 中的任何其他地方也将如此。


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


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

foo=$(echo|test)
Run Code Online (Sandbox Code Playgroud)

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