考虑源代码:
1.Parent.sh
#!/usr/bin/ksh
# No tee
ksh Child.sh;
exit_status=$?;
echo "Exit status: ${exit_status}"
# Using tee
ksh Child.sh | tee -a log.txt;
exit_status=$?;
echo "Exit status: ${exit_status}"
2.Child.sh
#!/usr/bin/ksh
...
exit 1;
输出:
Exit status: 1
Exit status: 0
- 变量
$exit_status
正在捕获 Child.sh 的退出状态,因此也是如此1
。 - 在第二种情况下,
$exit_status
正在捕获 tee 的退出状态,即0
。
那么如何捕获退出状态并使用 tee 呢?
答案1
转载(并改进)自comp.unix.shell 常见问题解答(因为我碰巧写了常见问题解答的这一部分):
如何在cmd1|cmd2中获取cmd1的退出代码
首先,请注意 cmd1 退出代码可能非零,但并不意味着错误。例如,这种情况发生在
cmd | head -n 1
您可能会观察到 141(或 ksh93 为 269,yash 为 397)退出状态cmd
,但这是因为在读取一行后终止cmd
时被 SIGPIPE 信号中断 。head -n 1
了解管道元素的退出状态
cmd1 | cmd2 | cmd3
使用 zsh(和 Fish 3.1+):
退出代码在特殊数组中提供pipestatus
。
cmd1
退出代码在 中$pipestatus[1]
,cmd3
退出代码在 中
$pipestatus[3]
,因此$status
/$?
始终与 相同
$pipestatus[-1]
。
与bash:
退出代码在特殊数组中提供PIPESTATUS
。
cmd1
退出代码位于 中${PIPESTATUS[0]}
,cmd3
退出代码位于 中
${PIPESTATUS[2]}
,因此始终与(或对于 4.2 之前的版本)$?
相同 。${PIPESTATUS[-1]}
${PIPESTATUS[@]: -1}
与任何其他类似 Bourne 的 shell
您需要使用一种技巧将退出代码传递到主 shell。您可以使用管道(2) 来完成此操作。您不是运行,而是cmd1
运行cmd1; echo "$?"
并确保 $?进入外壳。
exec 3>&1
code=`
# now, inside the backticks, fd4 goes to the pipe
# whose other end is read and stored in $code for
# later evaluation; fd1 is the normal standard output
# preserved the line before with exec 3>&1
exec 4>&1 >&3 3>&-
{
cmd1 4>&-; echo "ec1=$?;" >&4
} | {
cmd2 4>&-; echo "ec2=$?;" >&4
} | cmd3 4>&-
echo "ec3=$?;" >&4
`
exec 3>&-
eval "$code"
$ec1
、$ec2
、中的退出代码$ec3
。
使用 POSIX shell
您可以使用此功能来简化操作:
run() {
j=1
while eval "\${pipestatus_$j+:} false"; do
unset "pipestatus_$j"
j=$(($j+1))
done
j=1 com= k=1 l=
for arg do
case $arg in
('|')
com="$com {
$l "'3>&-
echo "pipestatus_'$j'=$?" >&3
} 4>&- |'
j=$(($j+1)) l=;;
(*)
l="$l \"\${$k}\""
esac
k=$(($k+1))
done
com="$com $l"' 3>&- >&4 4>&-
echo "pipestatus_'$j'=$?"'
{ eval "$(exec 3>&1; eval "$com")"; } 4>&1
j=1
ret=0
while eval "\${pipestatus_$j+:} false"; do
eval '[ "$pipestatus_'"$j"'" -eq 0 ] || ret=$pipestatus_'"$j"
j=$(($j+1))
done
return "$ret"
}
将其用作:
run cmd1 \| cmd2 \| cmd3
退出代码位于$pipestatus_1
、 、中$pipestatus_2
,$pipestatus_3
并且$?
是最右侧的非零退出状态(与pipefail
某些 shell 的选项类似)。
答案2
您可以使用 && 和 ||在 bash 中执行此操作 - 我认为类似的操作在 ksh 中也能工作。
ksh Child.sh && exit_status="$?" || exit_status="$?" | tee -a log.txt;
编辑:
正如@Stephane指出的,A && B | C
将输出A到stdout,并且仅通过管道将B传送到C。它需要对输出进行分组,将它们通过管道连接在一起。您无法将变量从子 shell 传递到调用方,但是,您可以这样做:
x=$(tempfile) && exit_status=$(ksh Child.sh > $x; echo $?) && (cat $x; rm $x) | tee -a log.txt