我想在Bash中执行一个长时间运行的命令,并捕获它的退出状态,并输出它的输出。
所以我这样做:
command | tee out.txt
ST=$?
问题是变量ST捕获tee的退出状态,而不是命令的退出状态。我怎么解决这个问题?
注意,该命令长时间运行,将输出重定向到文件以供以后查看,这对我来说不是一个好的解决方案。
我想在Bash中执行一个长时间运行的命令,并捕获它的退出状态,并输出它的输出。
所以我这样做:
command | tee out.txt
ST=$?
问题是变量ST捕获tee的退出状态,而不是命令的退出状态。我怎么解决这个问题?
注意,该命令长时间运行,将输出重定向到文件以供以后查看,这对我来说不是一个好的解决方案。
当前回答
在Ubuntu和Debian中,你可以apt-get install moreutils。这包含一个名为mispipe的实用程序,它返回管道中第一个命令的退出状态。
其他回答
纯壳方案:
% 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
现在第二只猫被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
请注意,第一个cat也失败了,因为它的stdout关闭了。在本例中,日志中失败命令的顺序是正确的,但不要依赖它。
此方法允许捕获单个命令的stdout和stderr,以便在发生错误时将其转储到日志文件中,或者在没有错误时删除它(如dd的输出)。
这个解决方案不需要使用bash特定的特性或临时文件。好处:最后,退出状态实际上是一个退出状态,而不是文件中的某个字符串。
情境:
someprog | filter
您需要someprog的退出状态和filter的输出。
以下是我的解决方案:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
请参阅我在unix.stackexchange.com上对同一问题的回答,以获得详细的解释和没有子shell和一些注意事项的替代方案。
有一个内部Bash变量叫做$PIPESTATUS;它是一个数组,保存最后一个前台命令管道中每个命令的退出状态。
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
或者另一种也适用于其他shell(如zsh)的选择是启用pipefail:
set -o pipefail
...
由于语法略有不同,第一个选项不能用于zsh。
有一个数组为您提供管道中每个命令的退出状态。
$ 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中最简单的方法是使用进程替换而不是管道。有几个不同之处,但它们可能对你的用例不太重要:
当运行管道时,bash等待所有进程完成。 向bash发送Ctrl-C使其杀死管道的所有进程,而不仅仅是主要进程。 pipefail选项和PIPESTATUS变量与进程替换无关。 可能更
使用进程替换,bash只是启动进程,然后忘记它,它甚至在作业中都不可见。
除了上面提到的差异,消费者< <(生产者)和生产者|消费者本质上是相同的。
如果想要转换哪个进程是“主”进程,只需将命令和替换方向转换为生产者> >(消费者)。在你的情况下:
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
正如我所说,它与管道表达式有不同之处。进程可能永远不会停止运行,除非它对管道关闭很敏感。特别是,它可能会不断向您的标准输出写入内容,这可能会令人困惑。