在BASH / SHELL中捕获输出和退出代码

11

我在使用shell时遇到了捕获输出和退出代码的问题。

我需要比较两个脚本的退出代码,如果它们不匹配,我想要输出我的两个脚本的输出内容。

目前我拥有的代码:

#!/bin/bash

resultA=$(./a.out 2>&1)
exitA=$?
resultB=$(./b.out 2>&1)
exitB=$?

问题在于可能出现的分段错误消息没有被捕获,因为它被定向到我的当前 shell 的错误输出,但我需要捕获所有内容,包括诸如分段错误之类的内容。

以下是一种解决方法,虽然不够详细,但可以起到一定的作用:

#!/bin/bash

resultA=$(./a.out 2>&1)
exitA=$?
resultB=$(./b.out 2>&1)
exitB=$?
if [ $exitA == 139 ]; then
    resultA=$resultA"Segmentation Fault"
fi

这至少使得“分段错误”出现在我的结果变量中。


1
我有点困惑:你的第二个例子并没有比第一个例子包含更多的子shell。 - Michael Jaros
这里的问题是你想将段错误消息捕获到变量中,但这并没有起作用吗? - Etan Reisner
你不能这样做 :) 如果你真的需要那段文本(例如为了报告),最好的方法是在你的脚本中生成它,如果你的退出代码指示了 SIGSEGV(请参见下面的答案)。 - Michael Jaros
@cdarke 对不起我只是从半记忆中写下来的,谢谢你提醒我语法并不完全正确。 - Flo
@MichaelJaros,我现在有一个可行的解决方案了,看来你是错的。 - Flo
显示剩余4条评论
3个回答

9

捕获段错误信息是可能的,但需要花费一些功夫。

以下是其中一种方式:

outputA=$(bash -c '(./a)' 2>&1)

这里我们创建了一个子shell(使用bash -c),将其stderr重定向到stdout,然后让该子shell在显式子shell中执行程序。子shell内部的错误将被子bash捕获,随后生成一个错误消息(这与交互式bash产生的消息略有不同):

$ echo $outputA
bash: line 1: 11636 Segmentation fault (core dumped) ( ./a )

1
@hek2mgl:任何进程都可以通过SIGCHLD捕获死亡的子进程,bash也是如此。这与核心转储的创建有些独立;正如您所说,当产生核心转储时(如果您保留了该配置),内核会触发apport,但核心转储不会抑制父进程的通知。(父进程会被通知核心转储。请参见sigaction,并搜索CLD_DUMPED) - rici
1
@hek2mgl:此外,apport并不会产生Segfault错误消息;bash会。关键是让bash重定向错误消息,并确保进行重定向的bash是产生错误消息的那个。因此,需要两个间接层级。 - rici
感谢您的解释,特别是关于CLD_DUMPED的信息 - 我想知道如果不安全地创建了核心转储,bash怎么会知道“核心已转储”。 - hek2mgl
谢谢@rici,还有一件事,我说过我也需要退出代码,以便比较两个程序版本。这是否意味着我必须执行两次程序才能获取消息和退出代码?或者我可以以某种方式传递子shell的退出代码并将其保存在变量中?在我的原始帖子中,第一个代码块在一次执行中保存输出和退出代码(减去SEGFAULT MSG)。 - Flo
好的,谢谢@rici。我已经弄清楚了:outputA=$(bash -c '(./a.out); exit $?' 2>&1)exitA=$? - Flo
显示剩余3条评论

6
感谢 @rici,这是解决我的问题的完整方案:
#!/bin/bash

resultA=$(bash -c '(./a.out); exit $?' 2>&1)
exitA=$?
resultB=$(bash -c '(./b.out); exit $?' 2>&1)
exitB=$?

为什么运行程序必须在括号中?这个命令'./a.out; exit $?'还能用吗? - masonCherry
1
@masonCherry:实际上,我的答案中已经解释了这一点(虽然简要)。如果a.out产生了段错误,那么你需要使用bash -c '(./a.out)'来捕获错误消息。你不需要exit $?来传递退出代码。但是,如果你使用./a.out; exit $?,那么你就不需要子shell(用括号创建),因为bash -c需要创建一个子shell来执行两个命令。 - rici

0

查看hek2mgl的答案,了解为什么输出中缺少消息。

Bash manpage 提供了一个解决方案的提示:

当命令因致命信号N而终止时,bash使用128+N的值作为退出状态。

您可以使用这个来处理一个信号杀死了你的子进程的特殊情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接