5

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?

musiphil
  • 1,611
  • 2
  • 14
  • 16
  • Have a look at this question: http://stackoverflow.com/q/985876/51831 and this one: http://stackoverflow.com/q/2413166/51831 – jpalecek Jul 02 '12 at 23:32

2 Answers2

5

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 }
'
  • Doesn't count | { read -r R Q; … } execute read in a subshell? I tried echo 2 3 | { read -r R Q; }; echo $R $Q and it prints out nothing. I like the answer using <<<"$count", though. – musiphil Jul 02 '12 at 23:53
  • Testing whether the input is empty (either with if (NR) or with ifne) is not good, sinceqstat` can really output nothing and exit successfully if there are no processes. – musiphil Jul 02 '12 at 23:55
  • @musiphil Yes. Put whatever uses $R and $Q inside the braces, e.g. count | { read -r R Q; echo $R $Q; } – Gilles 'SO- stop being evil' Jul 02 '12 at 23:55
  • I need to use the values of the variables in future statements, which is why I used process substitution in the beginning. count | { read -r R Q; echo $R $Q; } is barely different from count. – musiphil Jul 03 '12 at 00:04
  • 1
    @musiphil Another method is to filter the exit code inside awk. – Gilles 'SO- stop being evil' Jul 03 '12 at 00:04
  • Another trick is: { qstat -tn1 && echo "SUCCESS"; } | awk ' BEGIN { R = 0; Q = 0; } $10 == "R" { R++ } $10 == "Q" { Q++ } $0 == "SUCCESS" { print R, Q }' – musiphil Jul 03 '12 at 00:23
  • Passing the exit status through the pipe is a nice trick, though it may not be fully generalizable (i.e. we may not be able to distinguish our "special code" if the stream transferred through the pipe can be of an arbitrary format). I'm settling on what I wrote in the previous comment. Thanks a lot! – musiphil Jul 03 '12 at 00:28
  • @musiphil You used the same “special code” — SUCCESS, included only when the exit code is 0. If you can't make the last line unambiguous, modify the awk script so that it treats the last line specially, and then you simply echo $? after the input-producing command. – Gilles 'SO- stop being evil' Jul 03 '12 at 07:42
  • Yes, I know that I used a special code and that the my trick above is not fully generalizable; I was criticizing my own solution. ;-) It would be nice to have a separate channel to pass the exit status, but for this particular problem, the special code works well enough. – musiphil Jul 03 '12 at 07:50
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.

bahamat
  • 39,666
  • 4
  • 75
  • 104
  • 1
    A || B | C binds like A || { B | C; }, so if A succeeds then neither B nor C is executed. However, { A || B; } | C is a working solution; thanks for the idea. – musiphil Jul 03 '12 at 00:01
  • Yes, you're right. I've done it recently with a || b && c, which binds like { a || b } && c which is why it didn't occur to me. Updated. – bahamat Jul 03 '12 at 18:02
  • A variant of the idea is to print and detect something that indicates success: { qstat -tn1 && echo SUCCESS; } | awk '... $0 == "SUCCESS" { print... }' – musiphil Jun 19 '14 at 21:40