How do I capture the return status and use tee at the same time in korn shell?
Reproduced (and improved) from the comp.unix.shell FAQ (since I happen to have written that section of the FAQ):
How do I get the exit code of cmd1 in cmd1|cmd2
First, note that cmd1 exit code could be non-zero and still don't mean an error. This happens for instance in
cmd | head -n 1
you might observe a 141 (or 269 with ksh93, or 397 with yash) exit status of cmd
,
but it's because cmd
was interrupted by a SIGPIPE signal when
head -n 1
terminated after having read one line.
To know the exit status of the elements of a pipeline
cmd1 | cmd2 | cmd3
with zsh (and fish 3.1+):
The exit codes are provided in the pipestatus
special array.
cmd1
exit code is in $pipestatus[1]
, cmd3
exit code in
$pipestatus[3]
, so that $status
/$?
is always the same as
$pipestatus[-1]
.
with bash:
The exit codes are provided in the PIPESTATUS
special array.
cmd1
exit code is in ${PIPESTATUS[0]}
, cmd3
exit code in
${PIPESTATUS[2]}
, so that $?
is always the same as
${PIPESTATUS[-1]}
(or ${PIPESTATUS[@]: -1}
for versions older than 4.2).
with any other Bourne like shells
You need to use a trick to pass the exit codes to the main shell. You can do
it using a pipe(2). Instead of running cmd1
, you run cmd1; echo "$?"
and make
sure $? makes its way to the shell.
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"
Exit codes in $ec1
, $ec2
, $ec3
.
with a POSIX shell
You can use this function to make it easier:
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"
}
Use it as:
run cmd1 \| cmd2 \| cmd3
exit codes are in $pipestatus_1
, $pipestatus_2
, $pipestatus_3
and $?
is the right-most non-zero exit status (like with the pipefail
option of some shells).