管道输出和捕捉出口状况在庆典
-
10-07-2019 - |
题
我想要执行一个长期运行的命令在庆典,以及两捕获其退出的状态, 开球 其输出。
因此,我这样做:
command | tee out.txt
ST=$?
问题是,可变圣捕捉出口状况 tee
而不是命令。我如何能解决这个?
注意到命令是长期运行和重新定向将输出的文件进行查看它后来就不是一个很好的解决方案我。
解决方案
有被称为$PIPESTATUS
内部击变量;它是保持在命令的最后一次前台管道的每个命令的退出状态的数组。
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
或者另一替代方案,其也可以与其它壳(如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]
和在子shell执行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外壳的解决方案:
# 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.
我觉得这是最好的由内而外的解释 - 命令1将执行并打印在标准输出上的常规输出(文件描述符1),那么一旦它的完成,printf的将执行,并在标准输出打印icommand1的退出代码,但标准输出被重定向到文件描述符3。
虽然是command1运行时,它的标准输出被输送到命令2(printf的输出从未使得它command2的,因为我们送它到文件描述符3,而不是1,这是管道在读什么)。然后我们重定向命令2的输出文件描述符4,因此,它也保持了文件描述符1 - 因为我们想文件描述符1免费一点点过去了,因为我们将带来文件描述符3中的printf输出回落到文件描述符1 - 因为这是命令替换(反引号),将捕捉什么,这就是将得到放入变量
魔术的最后一点是,第一exec 4>&1
我们没有作为一个独立的命令 - 它打开文件描述符4作为外壳的stdout的副本。命令替换将捕捉无论是写在标准输出从它里面的命令的角度看 - 但由于命令2的输出是要尽可能的命令替换来说文件描述符4,命令替换不捕获它 - 但是一旦它得到命令替换它实际上仍然要脚本的整体文件描述符1的“OUT”。
(该$?
必须是一个独立的命令,因为当您尝试写入命令替换里面文件描述符许多普通弹不喜欢它,那就是在“外部”命令,打开正在使用置换。因此,这是这样做的最简单的可移植的方式。)
可以看看它在一个更小的技术,更有趣的方式,仿佛命令的输出蛙跳相互:命令1管道命令2,则printf的输出跃过命令2,使得命令2不抓住它,然后命令2的输出跃过进出命令替换,就像printf的土地只是在时间的话,被替代捕捉,使其在变量结束,和Command的输出为得意扬扬地写到标准输出,就如同在正常的管子。
另外,据我所知,( )
仍将包含在管中的第二命令的返回代码,因为变量赋值,命令替换和化合物命令都是有效透明的命令的内部他们返回代码,所以命令2的返回状态应该得到传播出去 - 这一点,并没有定义一个附加功能,这就是为什么我认为这可能是比lesmana提出了一个稍微更好的解决方案。
每告诫lesmana提到,它可能是命令1将在某个时候最终使用文件描述符3或4,所以要更加强劲,你会怎么做:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
请注意,我在我的例子中使用的化合物的命令,但子shell(使用{ }
代替3>&1
也将工作,虽然也许是效率较低。)
命令继承文件描述符从一个启动它们的进程,所以整个第二线将继承文件描述符4,并且该化合物命令后跟4>&-
将继承文件描述符3。所以3>&-
可确保内部复合命令将不会继承文件描述符4个,在<=>不会继承文件描述符三相,从而命令1获得了“清洁剂”,更标准的环境。你也可以移动内<=>旁边<=>,但我想,为什么不只是限制它的范围尽可能地。
我不知道事情是如何经常使用文件描述符三个和四个直接 - 我想大多数的时间程序的使用返回未使用的,在最时刻的文件系统调用descriptors,但有时代码直接写到,我想文件描述符3(我能想象一个程序检查文件描述符,看它是否是开放的,并使用它,如果它是,或不同因此,如果它不行为)。因此后者可能是最好记住并使用通用的情况。
在Ubuntu和Debian,可以apt-get install moreutils
。这包含一个称为mispipe
实用程序,返回管中的第一命令的退出状态。
PIPESTATUS [@]必须在管命令返回之后立即被复制到一个数组。 任何读取PIPESTATUS的[@]将擦除的内容。 它复制到另一个阵列如果您计划在检查所有管道指令的状态。 “$?”是相同的值的最后一个元素“$ {PIPESTATUS [@]}”, 和阅读它似乎消灭“$ {PIPESTATUS [@]}”,但我还没有完全验证了这一点。
declare -a PSA
cmd1 | cmd2 | cmd3
PSA=( "${PIPESTATUS[@]}" )
如果管道是在副壳这将不起作用。对于解决这一问题,结果 请参阅的bash PIPESTATUS在backticked命令?
(command | tee out.txt; exit ${PIPESTATUS[0]})
不像@ CODAR的回答这个返回的第一个命令的原始退出代码,而不是只有0成功和127失败。但随着@Chaoran指出,你可以叫${PIPESTATUS[0]}
。然而,重要的是所有被放入括号中。
庆典之外,你可以这样做:
bash -o pipefail -c "command1 | tee output"
其中壳预计这是例如在忍者脚本有用/bin/sh
最简单的方式做到这一点,在平bash是使用 进程替换 而不是一个管道。有几种差异,但它们可能不是问题很多为你使用情况:
- 当运行一个管道,庆典等待,直到所有进程的完成。
- 发送Ctrl-C来砸让它杀了所有的进程的一个管道,而不仅仅是主要原因之一。
- 的
pipefail
选项和PIPESTATUS
变量是无关紧要的过程替代。 - 可能更多
与过程的替代,庆典刚刚开始的进程,并忘记它,甚至没有可见 jobs
.
所提到的分歧放在一边, consumer < <(producer)
和 producer | consumer
基本上是相等的。
如果你要翻哪一个是"主要的"过程中,你只是翻转的命令和方向的替换 producer > >(consumer)
.在你的情况:
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
正如我所说的,有的差异从管道的表达。该过程可能永远不会停止运行,除非它是敏感的管关闭。特别是,它可能会继续写的东西给你的标准输出,这可能会造成混淆。
纯壳溶液:
% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag && (echo Some command failed: ; cat error.flag)
hello world
和现在与第二cat
通过false
取代:
% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141
请注意第一猫也将失败,因为它的标准输出得到它关闭。在日志中失败的命令的顺序是在这个例子中是正确的,但不依赖于它。
这个方法允许捕捉stdout和stderr用于各个命令,这样你就可以转储以及到日志文件中,如果发生错误,或者删除它,如果没有错误(如DD的输出)。
在@布赖恩-S-Wilson的答案碱;此bash的辅助函数:
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
它有时可能是更简单和更清晰的使用外部的命令,而不是挖到的细节庆典。 管道, 从最小的过程脚本语言 execline, 退出与返回代码的第二个命令*就像一个 sh
管道,但是不像 sh
, 它允许扭转方向的管道,以便我们可以捕捉的返回代码的生产商进程(下面是所有的 sh
命令行,但与 execline
安装):
$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world
$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt
hello world
$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world
$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1
$ pipeline -w tee out.txt "" true; echo $?
0
$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world
使用 pipeline
具有同样的差别地bash管道的庆典的过程替代使用中的答案 #43972501.
*实际上 pipeline
不会退出,除非有一个错误。它的执行进入第二命令,所以它的第二命令,该命令没有返回。