Get exit code of a piped background process

2019-07-22 12:22发布

问题:

someCommand 2>&1 | grep pattern &

How do I get the exit status of someCommand?

PIPESTATUS doesn't work because it's a background process.

I've found this link and it seems to work but only if I use it exactly that way. Simple echoing to the screen doesn't seem to work. I was wondering if it's possible to get the exit code without creating temporary files.

回答1:

In bash you could do :

echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Example :

$ ls -l | grep somefile
-rw-rw-r--  1 me me     32 May  4 15:47 somefile
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
0 0

$ ls -l 1>/dev/null | grep while
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
0 1

For piped foreground processes

In case of a script say testscript.sh which contains :

#!/bin/bash
echo "Some Stuff"
exit 29 # Some random exit code for testing

do

$./testscript.sh | grep somestuff
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
29 1

For piped background processes

Method 1: Using pipefail

For testscript.sh which contains :

#!/bin/bash
set -eo pipefail  
#set -o pipefail causes a pipeline to produce a failure return code
#If a command fails, set -e will make the whole script exit,
cat nonexistingfile # this command fails
echo "Some Stuff"
exit 29

Do

$ ./testscript.sh 2>/dev/null | grep Some &
[2] 7684
$ fg 2
bash: fg: job has terminated
[2]-  Exit 1         ./testscript.sh 2> /dev/null | grep --color=auto Some

You get an the exit status 1 from which you conclude that the script failed.

Had cat nonexistingfile been removed you would have got:

[2]-  Done                    ./37257668.sh 2> /dev/null | grep --color=auto Some

Disdvantage : pipefail will return a one for all exit code that is not specific to the command that failed

Method 2 : Source the shell script

$ . ./testscript.sh 2>/dev/null | grep Some & #mind the dot in the beginning
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
29 0

Final Touch

If you suspect a single command to fail in a shell script,test script, you could do below :

#no shebang
echo "Some Stuff"
ls non_existent 2>/dev/null || ls__return_value=50 

Do

$. ./testscript | grep "Some"

$if [ $ls__return_value -eq 50 ]; then echo "Error in ls"; fi


回答2:

With a temp file

You can put the code that saves the content of PIPESTATUS in a temp file inside { ... } and run it in background. This approach would be needed if we are interested in the exit code of more than one command in the pipeline:

{ 
  someCommand 2>&1 | grep -- pattern
  printf "0=%s\n1=%s\n" "${PIPESTATUS[0]}" "${PIPESTATUS[1]}" > status.out
} &
wait "$!"
# grab the exit code from status.out file

Without a temp file

{ 
  someCommand 2>&1 | grep -- pattern
  exit ${PIPESTATUS[0]}
} &
wait "$!"
echo $?   # this is the exit code of someCommand

wait: wait [n]

Wait for the specified process and report its termination status.  If
N is not given, all currently active child processes are waited for,
and the return code is zero.  N may be a process ID or a job
specification; if a job spec is given, all processes in the job's
pipeline are waited for.