Consider Source code:
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;
Output:
Exit status: 1 Exit status: 0
- Variable
$exit_statusis capturing the exit status of Child.sh and so is1. - In the 2nd case,
$exit_statusis capturing the exit status of tee, which is0.
So how do I capture the exit status and also use tee?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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).
Method 2
You can use && and || to do it in bash – I assume something similar will work in ksh.
ksh Child.sh && exit_status="$?" || exit_status="$?" | tee -a log.txt;
EDIT:
As @Stephane points out, A && B | C will output A to stdout, and only pipe B to C. It needs to group the outputs, to pipe them together. You can’t pass a variable from a subshell into the caller, but, you can do this:
x=$(tempfile) && exit_status=$(ksh Child.sh > $x; echo $?) && (cat $x; rm $x) | tee -a log.txt
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0