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

所以我这样做:

command | tee out.txt
ST=$?

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

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


当前回答

愚蠢的解决方案:通过命名管道(mkfifo)连接它们。然后可以再次执行该命令。

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?

其他回答

为什么不使用stderr?像这样:

(
     # Our long-running process that exits abnormally
    ( for i in {1..100} ; do echo ploop ; sleep 0.5 ; done ; exit 5 )
    echo $? 1>&2 # We pass the exit status of our long-running process to stderr (fd 2).
) | tee ploop.out

所以ploop。Out接收标准输出。Stderr接收长时间运行的进程的退出状态。这样做的好处是完全与posix兼容。

(好吧,除了示例长时间运行的进程中的range表达式,但这并不真正相关。)

这是它的样子:

...
ploop
ploop
ploop
ploop
ploop
ploop
ploop
ploop
ploop
ploop
5

请注意,返回代码5不会输出到文件plop .out。

在普通bash中最简单的方法是使用进程替换而不是管道。有几个不同之处,但它们可能对你的用例不太重要:

当运行管道时,bash等待所有进程完成。 向bash发送Ctrl-C使其杀死管道的所有进程,而不仅仅是主要进程。 pipefail选项和PIPESTATUS变量与进程替换无关。 可能更

使用进程替换,bash只是启动进程,然后忘记它,它甚至在作业中都不可见。

除了上面提到的差异,消费者< <(生产者)和生产者|消费者本质上是相同的。

如果想要转换哪个进程是“主”进程,只需将命令和替换方向转换为生产者> >(消费者)。在你的情况下:

command > >(tee out.txt)

例子:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

正如我所说,它与管道表达式有不同之处。进程可能永远不会停止运行,除非它对管道关闭很敏感。特别是,它可能会不断向您的标准输出写入内容,这可能会令人困惑。

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

情境:

someprog | filter

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

以下是我的解决方案:

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

echo $?

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

有一个内部Bash变量叫做$PIPESTATUS;它是一个数组,保存最后一个前台命令管道中每个命令的退出状态。

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

或者另一种也适用于其他shell(如zsh)的选择是启用pipefail:

set -o pipefail
...

由于语法略有不同,第一个选项不能用于zsh。

基于@brian-s-wilson的回答;这个bash helper函数:

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

使用:

1: get_bad_things必须成功,但它不应该产生输出;但我们希望看到它确实产生的输出

get_bad_things | grep '^'
pipeinfo 0 1 || return

2:所有管道必须成功

thing | something -q | thingy
pipeinfo || return