Exiting a pipeline if a former command fails

I’m trying to check the number of running and queued PBS jobs by parsing the output of qstat -tn1 from a bash script.
So far, this has worked:

count ()
{
    qstat -tn1 | awk '
        BEGIN { R = 0; Q = 0; }
        $10 == "R" { R++ }
        $10 == "Q" { Q++ }
        END { print R, Q }'
}

if read -r R Q < <(count)
    ...

However, I see that qstat occasionally fails for unknown reasons. In that case, it prints nothing to stdout and some error message to stderr, and exits with a non-zero status (fairly standard). However, awk doesn’t know that qstat failed, and happily prints 0 0 for the empty input it received. Then read assigns 0 to both R and Q without knowing that qstat actually failed.

  1. I need to initialize R and Q with 0 in the BEGIN block of the awk script, because there may be no running processes or no queued processes, and I need to print 0, not just an empty string, for the number of such processes.
  2. I could do set -o pipefail, which would allow count to exit with a non-zero status, but read cannot see the exit status, and awk gets executed and prints 0 0 for the empty input anyway.
  3. I could try a named pipe and subprocesses, but having to manage them feels like an overkill too complicated.

Is there any good way to allow the caller of count to detect its failure?

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

I think reading count in a process substitution prevents you from obtaining its return status. So don’t do it. Instead, store the result in a variable, or use a pipe.

count=$(count)
if [ $? -eq 0 ]; then
  read -r R Q <<<"$count"
  …

or

set -o pipefail
if count | { read -r R Q; … }

Another possibility is to use the PIPESTATUS variable to check the return status of the first command.

count=$(qstat -tn1 | awk …)
if ((${PIPESTATUS[0]} == 0)); then
  read P Q
  …

Alternatively, arrange for the awk to print something distinctive (e.g. nothing) when its input is empty.

awk '
    BEGIN { R = 0; Q = 0; }
    $10 == "R" { R++ }
    $10 == "Q" { Q++ }
    END { if (NR) print R, Q }'

You can test if the input of a command is empty with ifne from moreutils or other methods. But since you’re piping into awk, you might as well do it right inside the awk script that you already have.

If you need to get the return status from the qstat command, you can feed it to awk as an extra input line. For easier parsing, arrange for the last line to have a unique format.

{
  qstat -tn1
  echo exit_code = $?
} | awk '
    …
    /^exit_code = / { status = $3 }
    END { if (status == 0) print Q, R }
'

Solution 2

This:

count ()
{
    { qstat -tn1 || echo "EPIC FAIL" } | awk '
        BEGIN { R = 0; Q = 0; }
        $10 == "R" { R++ }
        $10 == "Q" { Q++ }
        END { print R, Q }'
}

if read -r R Q < <(count)
    ...

If qstat is successful its output is sent to awk. If qstat returns non-zero status the text “EPIC FAIL” is sent to awk.

This is just an example. Replace the echo with something appropriate that you can handle inside awk or with if read.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply