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

所以我这样做:

command | tee out.txt
ST=$?

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

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


当前回答

为什么不使用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。

其他回答

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

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

有一个数组为您提供管道中每个命令的退出状态。

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1
(command | tee out.txt; exit ${PIPESTATUS[0]})

与@cODAR的回答不同,它返回第一个命令的原始退出码,不仅成功为0,失败为127。但是正如@Chaoran指出的,你可以直接调用${PIPESTATUS[0]}。但重要的是,所有的都放在括号里。

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

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

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

基于@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