我想在Bash中执行一个长时间运行的命令,并捕获它的退出状态,并输出它的输出。

所以我这样做:

command | tee out.txt
ST=$?

问题是变量ST捕获tee的退出状态,而不是命令的退出状态。我怎么解决这个问题?

注意,该命令长时间运行,将输出重定向到文件以供以后查看,这对我来说不是一个好的解决方案。


当前回答

在Ubuntu和Debian中,你可以apt-get install moreutils。这包含一个名为mispipe的实用程序,它返回管道中第一个命令的退出状态。

其他回答

PIPESTATUS[@]必须在管道命令返回后立即复制到一个数组中。 任何对PIPESTATUS[@]的读取都将删除该内容。 如果计划检查所有管道命令的状态,则将其复制到另一个数组。 $?与${PIPESTATUS[@]}的最后一个元素值相同, 读它似乎破坏了“${PIPESTATUS[@]}”,但我还没有完全验证这一点。

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

如果管道位于子外壳中,这将不起作用。为了解决这个问题, 查看bash pipestatus在backtick命令?

在Ubuntu和Debian中,你可以apt-get install moreutils。这包含一个名为mispipe的实用程序,它返回管道中第一个命令的退出状态。

使用外部命令有时可能比深入研究bash的细节更简单、更清晰。管道,来自最小的进程脚本语言execline,以第二个命令*的返回代码退出,就像sh管道一样,但与sh不同的是,它允许反转管道的方向,以便我们可以捕获生产者进程的返回代码(以下都是在sh命令行上,但安装了execline):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

使用管道与原生bash管道的区别与答案#43972501中使用的bash进程替换相同。

*实际上管道不退出,除非有一个错误。它执行到第二个命令中,所以是第二个命令执行返回。

纯壳方案:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

现在第二只猫被false取代:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

请注意,第一个cat也失败了,因为它的stdout关闭了。在本例中,日志中失败命令的顺序是正确的,但不要依赖它。

此方法允许捕获单个命令的stdout和stderr,以便在发生错误时将其转储到日志文件中,或者在没有错误时删除它(如dd的输出)。

这个解决方案不需要使用bash特定的特性或临时文件。好处:最后,退出状态实际上是一个退出状态,而不是文件中的某个字符串。

情境:

someprog | filter

您需要someprog的退出状态和filter的输出。

以下是我的解决方案:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

请参阅我在unix.stackexchange.com上对同一问题的回答,以获得详细的解释和没有子shell和一些注意事项的替代方案。