我想执行Bash中长时间运行的命令,都捕获它的退出状态,并且发球它的输出.
所以我这样做:
command | tee out.txt ST=$?
问题是变量ST捕获退出状态tee
而不是命令.我怎么解决这个问题?
请注意,命令长时间运行并将输出重定向到文件以便以后查看它对我来说不是一个好的解决方案.
有一个内部Bash变量叫$PIPESTATUS
; 它是一个数组,用于保存最后一个前台命令管道中每个命令的退出状态.
| tee out.txt ; test ${PIPESTATUS[0]} -eq 0
或者另一个也适用于其他shell(如zsh)的替代方法是启用pipefail:
set -o pipefail ...
第一个选项并不能一起工作zsh
,由于一点点不同的语法.
使用bash set -o pipefail
是有帮助的
pipefail:管道的返回值是以非零状态退出的最后一个命令的状态,如果没有以非零状态退出的命令则为零
愚蠢的解决方案:通过命名管道(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
此解决方案无需使用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]
中执行exit
命令的结果,您可以直接访问初始命令的返回值:
command | tee ; ( exit ${PIPESTATUS[0]} )
这是一个例子:
# the "false" shell built-in command returns 1 false | tee ; ( exit ${PIPESTATUS[0]} ) echo "return value: $?"
会给你:
return value: 1
所以我想提供像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(文件描述符1)上执行并打印其常规输出,然后一旦完成,printf将执行并在其stdout上打印icommand1的退出代码,但该stdout被重定向到文件描述符3.
当command1正在运行时,它的stdout被传送到command2(printf的输出永远不会使它成为command2,因为我们将它发送到文件描述符3而不是1,这是管道读取的内容).然后我们将command2的输出重定向到文件描述符4,这样它也不在文件描述符1之外 - 因为我们希望文件描述符1稍后释放,因为我们将把文件描述符3上的printf输出带回到文件描述符中1 - 因为这就是命令替换(反引号)将捕获的内容以及将放入变量的内容.
魔术的最后一点是exec 4>&1
我们首先作为一个单独的命令 - 它打开文件描述符4作为外壳的标准输出的副本.命令替换将从其中的命令的角度捕获在标准输出上写入的任何内容 - 但是因为命令替换涉及到command2的输出将转到文件描述符4,所以命令替换不捕获它 - 但是一旦它得到命令替换的"out"它实际上仍然会转到脚本的整个文件描述符1.
(exec 4>&1
必须是一个单独的命令,因为当您尝试写入命令替换中的文件描述符时,许多常见的shell不喜欢它,这在使用替换的"external"命令中打开.所以这是最简单的便携方式.)
您可以用技术性较低且更有趣的方式查看它,就好像命令的输出相互跳跃:command1管道到command2,然后printf的输出跳过命令2,这样命令2就不会捕获它,然后命令2的输出跳过命令替换,就像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(使用( )
而不是{ }
也可以工作,但可能效率较低.)
命令从启动它们的进程继承文件描述符,因此整个第二行将继承文件描述符4,后面的复合命令3>&1
将继承文件描述符3.因此,4>&-
确保内部复合命令不会继承文件描述符四,并且3>&-
不会继承文件描述符三,因此command1获得"更清洁",更标准的环境.你也可以移动内部4>&-
旁边的3>&-
,但我想为什么不尽可能地限制它的范围.
我不确定直接使用文件描述符三和四的情况 - 我认为大多数时候程序使用系统调用来返回当前未使用的文件描述符,但有时代码直接写入文件描述符3,猜测(我可以想象一个程序检查一个文件描述符,看看它是否打开,如果是,则使用它,如果不是则表现不同).所以后者可能最好记住并用于通用案例.
在Ubuntu和Debian中,你可以apt-get install moreutils
.这包含一个名为的实用程序mispipe
,它返回管道中第一个命令的退出状态.