Consider Source code:

1. Parent.sh

# No tee
ksh Child.sh;
echo "Exit status: ${exit_status}"
# Using tee
ksh Child.sh | tee -a log.txt;
echo "Exit status: ${exit_status}"

2. Child.sh

exit 1;


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?

  • 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 – Stéphane 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

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
  # 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() {
  while eval "\${pipestatus_$j+:} false"; do
    unset "pipestatus_$j"
  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}\""
  com="$com $l"' 3>&- >&4 4>&-
       echo "pipestatus_'$j'=$?"'

  { eval "$(exec 3>&1; eval "$com")"; } 4>&1
  while eval "\${pipestatus_$j+:} false"; do
    eval '[ "$pipestatus_'"$j"'" -eq 0 ] || ret=$pipestatus_'"$j"
  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).

| improve this answer | |
  • Should cmd3 be cmd3 4>&-, like cmd1 and cmd2? – hvd Apr 21 '18 at 6:24
  • 1
    @hvd, you're right cmd3 doesn't need that fd 4 either, so we might as well close it. See edit with a few other improvements. – Stéphane Chazelas Apr 21 '18 at 7:47

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;


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
| improve this answer | |
  • That pipes the (non-)output of exit_status="$?" to tee. – Stéphane 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. – Stéphane 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

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