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.

I have two processes foo and bar, connected with a pipe:

$ foo | bar

bar always exits 0; I'm interested in the exit code of foo. Is there any way to get at it?

share|improve this question

12 Answers 12

up vote 49 down vote accepted

If you are using bash, you can use the PIPESTATUS array variable to get the exit status of each element of the pipeline.

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0
share|improve this answer
5  
And pipestatus in zsh. Unfortunately other shells don't have this feature. –  Gilles Jun 2 '11 at 21:05
3  
Note: Arrays in zsh begin counterintuitively at index 1, so it's echo "$pipestatus[1]" "$pipestatus[2]". –  Legate Nov 14 '11 at 14:09
1  
You could check the whole pipeline like this: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi –  l0b0 Sep 25 '12 at 15:39

There are 3 common ways of doing this:

Pipefail

The first way is to set the pipefail option. This is the simplest and what it does is basically set the exit status $? to the exit code of the last program to exit non-zero (or zero if all exited successfully).

# false | true; echo $?
0
# set -o pipefail
# false | true; echo $?
1

$PIPESTATUS

Bash also has a variable called $PIPESTATUS which contains the exit status of all the programs in the last command.

# true | true; echo "${PIPESTATUS[@]}"
0 0
# false | true; echo "${PIPESTATUS[@]}"
1 0
# false | true; echo "${PIPESTATUS[0]}"
1
# true | false; echo "${PIPESTATUS[@]}"
0 1

You can use the 3rd command example to get the specific value in the pipeline that you need.

This solution might not be available though. I think $PIPESTATUS might have been added in a fairly recent version of bash, and your OS may not have it.

Separate executions

This is the most unwieldy of the solutions. Run each command separately and capture the status

# OUTPUT="$(echo foo)"
# STATUS_ECHO="$?"
# printf '%s' "$OUTPUT" | grep -iq "bar"
# STATUS_GREP="$?"
# echo "$STATUS_ECHO $STATUS_GREP"
0 1
share|improve this answer
1  
Darn! I was just gonna post about PIPESTATUS. –  slm Apr 21 '13 at 13:57
1  
For reference there are several other techniques discussed in this SO question: stackoverflow.com/questions/1221833/… –  slm Apr 21 '13 at 14:00
1  
@Patrick the pipestatus solution is works on bash , just more quastion in case I use ksh script you think we can find something similar to pipestatus ? , ( meenwhile I see the pipestatus not supported by ksh ) –  yael Apr 21 '13 at 14:32
1  
@yael I don't use ksh, but from a brief glance at it's manpage, it doesn't support $PIPESTATUS or anything similar. It does support the pipefail option though. –  Patrick Apr 21 '13 at 15:30
    
Great answer. I learned 2 things I never knew. :) –  Tim Kennedy Apr 30 '13 at 19:14

While not exactly what you asked, you could use

#!/bin/bash -o pipefail

so that your pipes return the last non zero return.

might be a bit less coding

Edit: Example

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
share|improve this answer
2  
set -o pipefail inside the script should be more robust, e.g. in case someone executes the script via bash foo.sh. –  maxschlepzig Jun 3 '11 at 9:17
    
How does that work? do you have a example? –  Johan Jun 8 '11 at 18:40
    
This works perfectly for me; I agree that it's more robust than the accepted solution insofar as you don't have to worry about indexes and making sure that you check the exit status on all your commands. Rather, you can simply run the pipe through bash -o pipefail -c "false | true" and if any piped command fails, echo $? will show it. –  tobias.mcnulty Apr 24 '12 at 14:55
    
Note that -o pipefail is not in POSIX. –  scy Jan 25 '13 at 15:15
    
This does not work in my BASH 3.2.25(1)-release. At the top of /tmp/ff I have #!/bin/bash -o pipefail. Error is: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name –  Felipe Alvarez Mar 24 at 6:01

What I do when possible is to feed the exit code from foo into bar. For example, if I know that foo never produces a line with just digits, then I can just tack on the exit code:

{ foo; echo $?; } | awk '!/[^0-9]/ {exit($0)} {…}'

Or if I know that the output from foo never contains a line with just .:

{ foo; echo .; echo $?; } | awk '/^\.$/ {getline; exit($0)} {…}'

This can always be done if there's some way of getting bar to work on all but the last line, and pass on the last line as its exit code.

If bar is a complex pipeline whose output you don't need, you can bypass part of it by printing the exit code on a different file descriptor.

exit_codes=$({ { foo; echo foo:$? >&3; } |
               { bar >/dev/null; echo bar:$? >&3; }
             } 3>&1)

After this $exit_codes is usually foo:X bar:Y, but it could be bar:Y foo:X if bar quits before reading all of its input or if you're unlucky. I think writes to pipes of up to 512 bytes are atomic on all unices, so the foo:$? and bar:$? parts won't be intermixed as long as the tag strings are under 507 bytes.

If you need to capture the output from bar, it gets difficult. You can combine the techniques above by arranging for the output of bar never to contain a line that looks like an exit code indication, but it does get fiddly.

output=$(echo;
         { { foo; echo foo:$? >&3; } |
           { bar | sed 's/^/^/'; echo bar:$? >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

And, of course, there's the simple option of using a temporary file to store the status. Simple, but not that simple in production:

  • If there are multiple scripts running concurrently, or if the same script uses this method in several places, you need to make sure they use different temporary file names.
  • Creating a temporary file securely in a shared directory is hard. Often, /tmp is the only place where a script is sure to be able to write files. Use mktemp, which is not POSIX but available on all serious unices nowadays.
foo_ret_file=$(mktemp -t)
{ foo; echo $? >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
share|improve this answer
    
When using the temporary file approach I prefer to add a trap for EXIT that removes all temporary files so that no garbage will be left even if the script dies –  miracle173 Oct 16 '13 at 20:16

Starting from the pipeline:

foo | bar | baz

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

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

Note the quotes around $error_statuses in my tests; without them grep can't differentiate because the newlines get coerced to spaces.

share|improve this answer

This solution works without using bash specific features or temporary files. Bonus: in the end the exit status is actually the exit status and not some string in a file.

Situation:

someprog | filter

you want the exit status from someprog and the output from filter.

Here is my solution:

stdintoexitstatus() {
  read exitstatus
  return $exitstatus
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | stdintoexitstatus) 4>&1

echo $?

Example:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

stdintoexitstatus() {
  read exitstatus
  return $exitstatus
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | stdintoexitstatus) 4>&1

echo $?

Output:

filtered line1
filtered line2
filtered line3
42

Lets examine that step by step.

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | stdintoexitstatus    #part2
) 4>&1                     #part1

From bottom up:

  1. A subshell is created with file descriptor 4 redirected to stdout. This means that whatever is printed to file descriptor 4 in the subshell will end up as the stdout of the entire construct.
  2. A pipe is created and the commands on the left (#part3) and right (stdintoexitstatus) are executed. stdintoexitstatus is also the last command of the pipe and that means the exit status of stdintoexitstatus will be the exit status of the entire construct.
  3. A subshell is created with file descriptor 3 redirected to stdout. This means that whatever is printed to file descriptor 3 in this subshell will end up in stdintoexitstatus and in turn will be the exit status of the entire construct.
  4. A pipe is created and the commands on the left (#part5 and #part6) and right (filter >&4) are executed. The output of filter is redirected to file descriptor 4. In #part1 the file descriptor 4 was redirected to stdout. This means that the output of filter is the stdout of the entire construct.
  5. Exit status from #part6 is printed to file descriptor 3. In #part3 file descriptor 3 was redirected to stdintoexitstatus. This means that the exit status from #part6 will be the final exit status for the entire construct.
  6. someprog is executed. The exit status is taken in #part5. The stdout is taken by the pipe in #part4 and forwarded to filter. The output from filter will in turn reach stdout as explained in #part4
share|improve this answer

With a bit of precaution, this should work:

(foo; echo $? >foo-status) | bar
foo_status=$(cat foo-status)
share|improve this answer
    
How about to cleanup like jlliagre? Don't you leave a file behind called foo-status? –  Johan Jun 8 '11 at 18:39
    
@Johan: If you prefer my suggestion, don't hesitate to vote it up ;) In addition not to leaving a file, it has the advantage of allowing multiple processes to run this simultaneously and the current directory need not to be writable. –  jlliagre Jun 8 '11 at 20:40

If you have the moreutils package installed you can use the mispipe utility which does exactly what you asked.

share|improve this answer

This is portable, i.e. works with any POSIX compliant shell, doesn't require the current directory to be writable, allows multiple scripts using the same trick to run simultaneously.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

Edit: here is a stronger version following Gilles' comments:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))
share|improve this answer
2  
This doesn't work for several reasons. 1. The temporary file may be read before it's written. 2. Creating a temporary file in a shared directory with a predictable name is insecure (trivial DoS, symlink race). 3. If the same script uses this trick several times, it'll always use the same file name. To solve 1, read the file after the pipeline has completed. To solve 2 and 3, use a temporary file with a randomly-generated name or in a private directory. –  Gilles Jun 8 '11 at 23:00
    
+1 Well the ${PIPESTATUS[0]} is easier but the basic idea here do work if one know about the problems that Gilles mentions. –  Johan Jun 9 '11 at 6:36
    
You can save a few subshells: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: I agree it's easier with Bash but in some contexts, knowing how to avoid Bash is worth it. –  dubiousjim Aug 29 '12 at 22:25

The following 'if' block will run only if 'command' succeeded:

if command; then
   # ...
fi

Specifically speaking, you can run something like this:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

Which will run haconf -makerw and store its stdout and stderr to "$haconf_out". If the returned value from haconf is true, then the 'if' block will be executed and grep will read "$haconf_out", trying to match it against "Cluster already writable".

Notice that pipes automatically clean themselves up; with the redirection you'll have to be carefull to remove "$haconf_out" when done.

Not as elegant as pipefail, but a legitimate alternative if this functionality is not within reach.

share|improve this answer

lesmana's solution above can also be done without the overhead of starting nested subprocesses by using { .. } instead (remembering that this form of grouped commands always has to finish with semicolons). Something like this:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

I've checked this construct with dash version 0.5.5 and bash versions 3.2.25 and 4.2.42, so even if some shells don't support { .. } grouping, it is still POSIX compliant.

share|improve this answer

EDIT: This answer is wrong, but interesting, so I'll leave it for future reference.


Adding a ! to the command inverts the return code.

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #
share|improve this answer
1  
I think this is unrelated. In your example, I want to know the exit code of ls, not invert the exit code of bogus_command –  Michael Mrozek Jun 2 '11 at 22:13
2  
I suggest to draw back that answer. –  maxschlepzig Jun 3 '11 at 9:15
    
Well, it appears I'm an idiot. I've actually used this in a script before thinking it did what the OP wanted. Good thing I didn't use it for anything important –  Falmarri Jun 8 '11 at 17:14

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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