将标准错误输出重定向到 bash 变量

将标准错误输出重定向到 bash 变量

这是一个 C 代码片段,它将导致segfault

// segfault.c

#include <string.h>

int main()
{
    memset((char *)0x0, 1, 100);
    return 1;
}

编译它:

gcc segfault.c -o segfault

如果从 bash 执行:

$ ./segfault
Segmentation fault (core dumped)

现在我将调用包装在 bash 脚本中。连续尝试了三次。我想获取变量内的错误输出ret并显示它。

#!/bin/bash

# segfault.sh

ret=`./segfault 2>&1`
echo "1) " $ret
ret=`eval ./segfault 2>&1`
echo "2) " $ret
ret=`eval ./segfault 2>&1 | cat`
echo "3) " $ret

如果我从 bash 执行脚本:

1) 
2) 
3)  ./segfault.sh: line 7: 28814 Segmentation fault (core dumped) ./segfault

显然,只有第三种调用形式有效。我的问题是,为什么前两种形式无法捕获错误输出?

答案1

它适用于我的简化 bash 脚本(仅限stderr):

$ cat seg.sh 
#!/bin/bash
echo "Segfault" 1>&2
$ test=`./seg.sh`; echo "x$test"
Segfault
x
$ test=`./seg.sh 2>&1`; echo "x$test"
xSegfault
$ test=`eval ./seg.sh 2>&1`; echo "x$test"
xSegfault

您的情况中的问题是由以下事实引起的:该Segmentation fault (core dumped)程序不是由您的程序编写的(因为它被内核杀死),而是由父进程编写,父进程获取有关其子进程死亡的信息。通过将其放入另一个进程并cat在上一个示例中通过管道来隐藏此效果。您应该依赖退出代码,然后依赖stderr

$ ./segfault; echo $?
Segmentation fault (core dumped)
139

答案2

消息“Segmentation failure (core dumped)”是由 bash 发出的,而不是由崩溃的程序发出的(当发出该消息时,程序已经死了!)。重定向仅适用于程序本身。

要重定向来自 shell 本身的有关程序的消息,请在 shell 分组构造内运行该程序,并重定向整个组的输出。最基本的 shell 分组结构是大括号,它除了分组之外什么也不做。

ret=`{ ./segfault; } 2>&1`

该表单ret=`eval ./segfault 2>&1`将重定向应用于eval命令的整个评估,因此原则上它应该可以工作,并且实际上它确实可以在我的 bash 4.3.30 及更早版本的机器上工作。可能发生的情况(我可以用 ksh 重现它)是您的 bash 版本进行了一些优化,以避免在子程序是子 shell 中的最后一个命令时分叉子程序。执行命令的名义方式ret=`eval ./segfault`是:

  • 创建一个管道。
  • fork,即创建一个shell子进程。在子流程(流程1)中:
    • 将输出重定向到管道。
    • 执行eval内置的.
    • 叉。在子流程(流程2)中:
      • 执行文件./segfault,即用该程序替换当前进程中正在运行的shell程序segfault
    • (进程1中)等待进程2完成。
    • 进程 1 退出。
  • (在原来的shell进程中)从管道读取数据并将数据累加到ret变量中。
  • 当管道关闭时,继续执行。

正如您所看到的,进程 1 创建了另一个进程,然后等待它完成,并立即退出。进程 1 进行自我回收会更有效。某些 shell(和 shell 版本)比其他 shell 更擅长识别此类情况并做出响应尾调用优化。但是,在 的情况下ret=`{ ./segfault; } 2>&1`,进程 2 将其标准错误重定向到文件描述符 1,但进程 1 则没有。在您尝试的 shell 版本中,优化器无法识别这种情况(它可能执行了尾部调用,但它应该以不同的方式设置重定向)。

相关内容