将命令输出导入到tee中,但也保存命令的退出代码

250

我有一个 shell 脚本,其中我封装了一个命令 (mvn clean install),将输出重定向到一个日志文件。

#!/bin/bash
...
mvn clean install $@ | tee $logfile
echo $? # Does not show the return code of mvn clean install
现在,如果mvn clean install失败并出现错误,我希望我的包装脚本也能因此而失败。但是,由于我将所有输出都传输到tee,所以我无法访问mvn clean install的返回代码,因此当我之后访问$?时,它始终为0(因为tee成功了)。
我尝试让该命令将错误输出写入一个单独的文件,并在之后进行检查,但是mvn的错误输出总是为空(似乎它只写入到stdout)。
如何保留mvn clean install的返回代码,同时仍将输出传输到日志文件?
4个回答

378
你可以设置pipefailshell选项来获得你想要的行为。
Bash参考手册中得知:
管道的退出状态是该管道中最后一个命令的退出状态,除非启用了pipefail选项(请参见内置指令集)。如果启用了pipefail,则管道的返回状态是具有非零状态退出的最后一个(右侧)命令的值,或者如果所有命令成功退出,则为零。
示例:
$ false | tee /dev/null ; echo $?
0
$ set -o pipefail
$ false | tee /dev/null ; echo $?
1

恢复原始的管道设置:

$ set +o pipefail

44
这个解决方案似乎比已被接受的解决方案更加优雅。 - Stefaan
4
同意。接受的答案要求你事先知道在你的管道中哪个命令将失败。如果你把五个不同的命令连成一条管道,你就必须猜测数组中哪一个会失败。 - noahlz
6
或者对PIPESTATUS执行循环。 - Joshua Olson
4
恢复原始的管道设置: $ set +o pipefail - eddie
3
在我看来,这应该是正确的答案:(set -o pipefail && false | tee log.txt) 将其在子shell中执行,以便恢复pipefail标志的原始状态。 - Jiri Kremser
显示剩余4条评论

216

由于您正在运行bash,所以可以使用其$PIPESTATUS变量,而不是$?

mvn clean install $@ | tee $logfile
echo ${PIPESTATUS[0]}

2
如下所述,如果您有多个管道,则需要检查每个命令的状态以了解其失败位置。 - Joshua Olson
6
非常重要的一点是,这个变量是短暂的,所以即使通过"echo"输出它,也会失去运行管道时的值。除非您将立即访问它且仅访问一次,否则请将其分配给另一个变量。 - Davor Cubranic

19
你可以运行mvn命令并缓存退出码...... 我在示例中使用了"false"命令。
$ { false ; echo $? > /tmp/false.status ; } | tee $logfile
$ cat /tmp/false.status
1
那么你可以使用状态文件内容来做出进一步的决策。我现在很好奇是否有更简洁的方法来完成这个任务。

1
我希望最终答案不是特定于shell的(即:仅限于bash)。 - Demosthenex
太好了!我更喜欢这个不仅仅依赖于bash :-) 感谢@Demosthenex - Ciges
但我使用了 ( ) 而不是 { } 来获取输出.... - Ciges

4

解决方法(注:@Frederic的解决方案更佳):

f=`mktemp`
(mvn clean install $@; echo $?>$f) | tee $logfile
e=`cat $f` #error in variable e
rm $f

1
我们能否使用 read e < $f 来代替 cat,以此节省系统资源? - Jens
当然。你可能想创建一个临时文件来避免竞争条件。但是,正如你所看到的,有更好的解决方案... - Karoly Horvath

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