当前位置:  开发笔记 > 编程语言 > 正文

在Bash中管道输出和捕获退出状态

如何解决《在Bash中管道输出和捕获退出状态》经验,为你挑选了8个好方法。

我想执行Bash中长时间运行的命令,都捕获它的退出状态,并且发球它的输出.

所以我这样做:

command | tee out.txt
ST=$?

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

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



1> codar..:

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

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

或者另一个也适用于其他shell(如zsh)的替代方法是启用pipefail:

set -o pipefail
...

第一个选项并不能一起工作zsh,由于一点点不同的语法.


这里有关于PIPESTATUS和Pipefail的例子有一个很好的解释:http://unix.stackexchange.com/a/73180/7453.
注意:$ PIPESTATUS [0]保存管道中第一个命令的退出状态,$ PIPESTATUS [1]保存第二个命令的退出状态,依此类推.
当然,我们必须记住这是特定于Bash的:如果我(例如)在我的Android设备上编写脚本以在BusyBox的"sh"实现上运行,或者在某些其他嵌入式平台上使用其他"sh"编写脚本变体,这不起作用.
对于那些关注不带引号的变量扩展的人:退出状态始终是无符号的8位整数[在Bash中](https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html),因此没有必要引用它.这通常也适用于Unix,其中[退出状态被明确定义为8位](http://pubs.opengroup.org/onlinepubs/9699919799/functions/_Exit.html),并且假设它是无符号的甚至通过POSIX本身,例如在定义其[逻辑否定]时(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_02_01).
您也可以使用`exit $ {PIPESTATUS [0]}`。

2> Felipe Alvar..:

使用bash set -o pipefail是有帮助的

pipefail:管道的返回值是以非零状态退出的最后一个命令的状态,如果没有以非零状态退出的命令则为零


如果您不想修改整个脚本的pipefail设置,可以只在本地设置该选项:`(set -o pipefail; command | tee out.txt); ST = $?`
@Jaan这将运行一个子shell.如果你想避免这种情况,可以执行`set -o pipefail`然后执行命令,然后立即执行`set + o pipefail`取消设置选项.

3> EFraim..:

愚蠢的解决方案:通过命名管道(mkfifo)连接它们.然后命令可以运行第二个.

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


这是这个问题的唯一答案,也适用于简单的**sh**Unix shell.谢谢!
虽然当你拥有bash额外功能的优势时bash的答案更加优雅,但这是更多的跨平台解决方案.这也是一般值得考虑的事情,因为每当你做一个长期运行的命令时,名称管道通常是最灵活的方式.值得注意的是,有些系统没有`mkfifo`,如果我没记错的话可能需要`mknod -p`.
为什么这个笨蛋?
@DaveKennedy:愚蠢的"显而易见,不需要复杂的bash语法知识"

4> Stefano Bori..:

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

$ 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



5> lesmana..:

此解决方案无需使用bash特定功能或临时文件即可运行.奖励:最后,退出状态实际上是退出状态,而不是文件中的某些字符串.

情况:

someprog | filter

你想要退出状态someprog和输出filter.

这是我的解决方案:

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

echo $?

请参阅我在unix.stackexchange.com上的相同问题的答案,以获取详细解释和替代方案,不带子壳和一些注意事项.



6> par..:

通过组合子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


谢谢,这允许我使用构造:``VALUE = $(might_fail | piping)``它不会在主shell中设置PIPESTATUS但会设置其errorlevel.通过使用:``VALUE = $(might_fail | piping;退出$ {PIPESTATUS [0]})``我想要我想要的.

7> mtraceur..:

所以我想提供像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,猜测(我可以想象一个程序检查一个文件描述符,看看它是否打开,如果是,则使用它,如果不是则表现不同).所以后者可能最好记住并用于通用案例.



8> Bryan Larsen..:

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

推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有