我希望在Bash中执行一个长时间运行的命令,并且捕获其退出状态,同时tee输出。
因此我会这样做:
command | tee out.txt
ST=$?
问题在于变量ST捕获了tee
的退出状态,而不是命令本身。我该怎么解决这个问题?请注意,该命令长时间运行,并将输出重定向到文件以便以后查看对我来说并不是一个好的解决方案。
我希望在Bash中执行一个长时间运行的命令,并且捕获其退出状态,同时tee输出。
因此我会这样做:
command | tee out.txt
ST=$?
问题在于变量ST捕获了tee
的退出状态,而不是命令本身。我该怎么解决这个问题?在Bash中有一个名为$PIPESTATUS
的内部变量;它是一个数组,用于保存最后一个前台管道命令中每个命令的退出状态。
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
或者另一种方法,也适用于其他shell(如zsh),就是启用pipefail:
set -o pipefail
...
由于语法略有不同,第一种选项在zsh
中不起作用。
exit ${PIPESTATUS[0]}
。该命令的作用与原文相同,意为退出当前脚本并返回管道中第一个命令的退出状态码。 - Chaoran mkfifo pipe
tee out.txt < pipe &
command > pipe
echo $?
pipename="pipe-$RANDOM" ; mkfifo "$pipename" ; tee -a "$LOG_FILE" < "$pipename" &
command > "$pipename" 2>&1
echo $?
rm "$pipename"
mkfifo
,而是需要使用mknod -p
,如果我记得正确的话。 - Haravikk使用bash的 set -o pipefail
很有帮助。
pipefail: 管道中最后一个退出状态非零的命令的返回值将作为整个管道的返回值; 如果没有命令退出状态非零,则返回值为零。
( set -o pipefail; command | tee out.txt ); ST=$?
。 - Jaanset -o pipefail
,然后执行命令,紧接着在命令之后执行set +o pipefail
取消该选项。 - Linus Arver-o pipefail
,他将知道管道是否失败,但如果'command'和'tee'都失败,他将收到'tee'的退出代码。 - t0r0X有一个数组,它提供了管道中每个命令的退出状态。
$ 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
这个解决方案不使用bash特定功能或临时文件。额外收获:最终退出状态实际上是一个退出状态而不是文件中的某个字符串。
情况:
someprog | filter
您想要从someprog
获取退出状态和从filter
获取输出结果。
这是我的解决方案:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
请查看我的回答,在unix.stackexchange.com上解释了详细内容,并提供了一个不涉及子shell和一些警告的替代方案。
通过将 PIPESTATUS[0]
和在子shell中执行的 exit
命令的结果结合起来,您可以直接访问初始命令的返回值:
command | tee ; ( exit ${PIPESTATUS[0]} )
这是一个例子:
# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"
将会给您:
返回值:1
VALUE=$(might_fail | piping)
,它不会在主shell中设置PIPESTATUS,但会设置其错误级别。通过使用:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
,我得到了我想要的结果。 - vaab所以我想贡献一个回答,类似于lesmana的,但我认为我的解决方案可能更简单,稍微更有优势,是一个纯Bourne shell的解决方案:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
我认为这最好从内部向外解释 - command1将执行并在标准输出(stdout)上打印其常规输出,然后一旦完成,printf将执行并在其stdout上打印icommand1的退出码,但是该stdout被重定向到文件描述符3。
当command1正在运行时,它的stdout被传输到command2(printf的输出永远不会传递给command2,因为我们将其发送到文件描述符3而不是1,这是管道读取的内容)。然后我们将command2的输出重定向到文件描述符4,以使它也保持离开文件描述符1 - 因为我们希望稍后留出一点文件描述符1的空间,因为我们将printf输出文件描述符3返回到文件描述符1 - 因为这就是命令替换(反引号)将捕获的内容,并且这就是将放置到变量中的内容。
魔法的最后一部分是我们作为单独命令所做的第一个exec 4>&1
- 它将文件描述符4打开为外部shell的stdout的副本。命令替换将捕获从其内部的命令角度写入标准输出的所有内容 - 但是由于command2的输出就像对于命令替换来说是将流到文件描述符4一样,命令替换不会捕获它 - 但是一旦它“脱离”命令替换,它实际上仍然流到脚本的整体文件描述符1。
(exec 4>&1
必须是单独的命令,因为许多常见的shell不喜欢您尝试在使用替换的“外部”命令内写入文件描述符。因此,这是最简单的可移植方法。)
你可以用一个不那么技术、更加玩味的方式来看待它,就好像命令的输出相互追逐:command1通过管道传输给command2,然后printf的输出跳过command2以便command2无法捕获,然后command2的输出跳过并从命令替换中出来,正好在printf及时抓住并被替换捕获,以便将其放置到变量中,而command2的输出则沿着正常的管道方式进入标准输出。
另外,据我所知,$?
仍然包含管道中第二个命令的返回代码,因为变量赋值,命令替换和复合命令都有效地对其中的命令的返回代码透明,因此command2的返回状态应该被传播出去 - 这也是为什么我认为这可能比lesmana提出的解决方案要好一些。
根据lesmana提到的注意事项,command1可能最终使用文件描述符3或4,因此为了更加健壮,你可以这样做:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
请注意,我的示例中使用了复合命令,但是子shell(使用( )
而不是{ }
也可以工作,尽管可能效率较低)。
命令会继承从启动它们的进程中继承的文件描述符,因此整个第二行将继承文件描述符四,后跟3>&1
的复合命令将继承文件描述符三。因此,4>&-
确保内部复合命令不会继承文件描述符四,3>&-
不会继承文件描述符三,因此command1获得了更“干净”和标准化的环境。您还可以将内部的4>&-
移动到3>&-
旁边,但我认为最好尽可能限制其范围。
我不确定有多少程序直接使用文件描述符三和四 - 我认为大多数时候程序使用返回未在使用中的文件描述符的系统调用,但有时代码会直接写入文件描述符3,我猜想(我可以想象一个程序检查文件描述符是否打开,如果打开,就使用它,否则根据情况采取不同的行动)。因此,对于通用情况,最好牢记后者并将其用于。
(command | tee out.txt; exit ${PIPESTATUS[0]})
( set +e ; command | tee ... )
来防止脚本在 tee
失败且外部脚本已经设置了 set -e
的情况下退出。 - joeytwiddle( ... exit )
比{ ... return }
更可取,因为尽管我们的子shell从父shell继承了set -e
,但我们的set +e
不会影响父shell。 - joeytwiddle在 bash 之外,您可以执行以下操作:
bash -o pipefail -c "command1 | tee output"
这在忍者脚本中非常有用,因为期望使用的 shell 是 /bin/sh
。
apt-get install moreutils
命令进行安装。这个包含了一个被称为mispipe
的实用程序,它将返回管道中第一个命令的退出状态。