13

How do I get the correct return code from a unix command line application after I've piped it through another command that succeeded?

In detail, here's the situation :

$ tar -cEvhf - -I ${sh_tar_inputlist} | gzip -5 -c > ${sh_tar_file}  --  when only the tar command fails $?=0
$ echo $?
0

And, what I'd like to see is:

$ tar -cEvhf - -I ${sh_tar_inputlist} 2>${sh_tar_error_file} | gzip -5 -c > ${sh_tar_file}
$ echo $?
1

Does anyone know how to accomplish this?

dogbane
  • 266,786
  • 75
  • 396
  • 414
SamiBOB
  • 243
  • 4
  • 12

4 Answers4

10

Use ${PIPESTATUS[0]} to get the exit status of the first command in the pipe.

For details, see http://tldp.org/LDP/abs/html/internalvariables.html#PIPESTATUSREF

See also http://cfajohnson.com/shell/cus-faq-2.html for other approaches if your shell does not support $PIPESTATUS.

Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
4

Look at $PIPESTATUS which is an array variable holding exit statuses. So ${PIPESTATUS[0]} holds the exit status of the first command in the pipe, ${PIPESTATUS[1]} the exit status of the second command, and so on.

For example:

$ tar -cEvhf - -I ${sh_tar_inputlist} | gzip -5 -c > ${sh_tar_file}
$ echo ${PIPESTATUS[0]}

To print out all statuses use:

$ echo ${PIPESTATUS[@]}
dogbane
  • 266,786
  • 75
  • 396
  • 414
4

Here is a general solution using only POSIX shell and no temporary files:

Starting from the pipeline: foo | bar | baz

exec 4>&1
error_statuses=`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`
exec 4>&-

$error_statuses contains the status codes of any failed processes, in random order, with indexes to tell which command emitted each status.

# if "bar" failed, output its status:
echo $error_statuses | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
echo $error_statuses | grep '2:' >/dev/null
sjngm
  • 12,423
  • 14
  • 84
  • 114
SamiBOB
  • 243
  • 4
  • 12
1

As others have pointed out, some modern shells provide PIPESTATUS to get this info. In classic sh, it's a bit more difficult, and you need to use a fifo:

#!/bin/sh

trap 'rm -rf $TMPDIR' 0
TMPDIR=$( mktemp -d )
mkfifo ${FIFO=$TMPDIR/fifo}

cmd1 > $FIFO &
cmd2 < $FIFO
wait $!
echo The return value of cmd1 is $?

(Well, you don't need to use a fifo. You can have the commands early in the pipe echo a status variable and eval that in the main shell, redirecting file descriptors all over the place and basically bending over backwards to check things, but using a fifo is much, much easier.)

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • Hello, i've tried this technique but it doesn't seams to work : I tested : /usr/bin/tar -cMEvhf - -I /tmp/test.tmp > $FIFO & gzip -5 -c < $FIFO > /tmp/test.bkz wait $! echo The return value of cmd1 is $? – SamiBOB Jan 16 '12 at 13:22
  • even when cmd1 fails, the $?=0 – SamiBOB Jan 17 '12 at 08:33