让我们定义一个函数来执行二进制文件:
function execute() { ./binary; }
然后定义第二个函数,将文本文件通过管道传输到第一个函数中:
function test() { cat in.txt | execute; }
如果binary
因段错误而崩溃,则从test
CLI 调用将导致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
,以便管道故障导致空扩展。另一种方法是故意暂时抑制诊断消息。