Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

This question already has an answer here:

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_status is capturing the exit status of Child.sh and so is 1.
  • In the 2nd case, $exit_status is capturing the exit status of tee, which is 0.

So how do I capture the exit status and also use tee?

share|improve this question
    
Hi @lesmana - Note that this question is asking for a ksh solution. Using $PIPESTATUS only works in bash - all solution I can find on unix.stackexchange.com are for bash. –  Kent Pawar May 17 '13 at 8:46
    
Seems the solution (workaround) for ksh is to use Pipefail.. I will test and confirm the same assuming this question is not incorrectly closed by then.. –  Kent Pawar May 17 '13 at 8:49
1  
I am aware that this question is marked ksh. I suggested the duplicate because the other question is not marked as bash specific and not all answers are bash specific. –  lesmana May 17 '13 at 8:52
    
In answers to that other question, you'll find POSIX answers. See also the comp.unix.shell FAQ –  Stephane Chazelas May 17 '13 at 9:01
    
Gotcha @lesmana. Well this solution explains pipefail well, which can be used for korn. Reference –  Kent Pawar May 17 '13 at 9:09
show 1 more comment

marked as duplicate by lesmana, Stephane Chazelas, jasonwryan, slm, vonbrand May 17 '13 at 12:39

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

2 Answers

up vote 5 down vote accepted

Reproduced from the comp.unix.shell FAQ (since CFAJ's site seems to be down at the moment and 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) 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:

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 $? 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}.

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 it way to the shell.

exec 3>&1
eval `
  # now, inside the backticks, fd4 goes to the pipe
  # whose other end is read and passed to eval;
  # 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
  echo "ec3=$?;" >&4
`
exec 3>&-

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 a; do
    if [ "x$a" = 'x|' ]; then
     com="$com { $l "'3>&-
                 echo "pipestatus_'$j'=$?" >&3
               } 4>&- |'
     j=$(($j+1)) l=
    else
     l="$l \"\${$k}\""
    fi
    k=$(($k+1))
  done
  com="$com $l"' 3>&- >&4 4>&-
            echo "pipestatus_'$j'=$?"'
  exec 4>&1
  eval "$(exec 3>&1; eval "$com")"
  exec 4>&-
  j=1
  while eval "\${pipestatus_$j+:} false"; do
    eval "[ \"\$pipestatus_$j\" -eq 0 ] || return \"\$pipestatus_$j\""
    j=$(($j+1))
  done
  return 0
}

Use it as:

run cmd1 \| cmd2 \| cmd3

exit codes are in $pipestatus_1, $pipestatus_2, $pipestatus_3 and $? is the first non-zero exit status.

share|improve this answer
add comment

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
share|improve this answer
    
That pipes the (non-)output of exit_status="$?" to tee. –  Stephane Chazelas May 17 '13 at 14:26
    
@StephaneChazelas Yes, but it also pipes the output of child.sh to tee. Try it yourself: echo a && exit_status="$?" | cat -. You'll see it still echoes "a" –  Benubird May 17 '13 at 15:36
    
You'll see "a" as well with echo a && whatever | tr a b, since that's running echo a and then run whatever | tr a b if echo a was successful. –  Stephane Chazelas May 17 '13 at 18:11
    
@StephaneChazelas Yes, it seems you're right. My mistake - I'll update the answer now. –  Benubird May 22 '13 at 11:44
add comment

Not the answer you're looking for? Browse other questions tagged or ask your own question.