3

I want to run a series of tests (each on a different PID), and derive a successful status only if all tests exit successfully. Something like

check $PID1 && check $PID2 && check $PID3

but for an indeterminate number of tests. How can I accomplish this?

5
  • 1
    In what format is your "indeterminate" command list? Commented Oct 16, 2019 at 15:12
  • Do you want to continue running the tests if one fails, or exit immediately? Commented Oct 16, 2019 at 15:13
  • The title ("all/any") is a little broader than the body ("all"); which one do you need? Commented Oct 16, 2019 at 15:18
  • I need all in this case; just interested in both (and of course negating any can give the effect of all) Commented Oct 16, 2019 at 15:21
  • What is tag "test" doing? The definition seems clear... Commented Oct 17, 2019 at 2:32

4 Answers 4

8

That shouldn't be too hard to write out as a loop:

pids=(1025 3425 6474)
check_all() {
    for pid in "$@"; do
        if ! check "$pid"; then
            return 1
        fi
    done
}
check_all "${pids[@]}"

Like the chain of commands linked with &&, the function will stop on the first failing check.

Though do note that I replaced your variables PID1, PID2 etc. with a single array. Bash could iterate over variables whose names start with a particular string, but arrays are just more convenient. (Unless those variables come from the outside of the script through the environment where you can't pass proper arrays.)

Also, I hard-coded the check command in the loop here. You could pass distinct commands for the function to run, but that's ripe with issues with word splitting and quote handling. (See here and here.)

15
  • You could also use a subshell declaration for the function: check_all() ( ... ) and then add set -e, then you can get rid of the whole if ! ... ; then return 1; fi part and just check $pid as only command in the for-loop. (see). Commented Oct 16, 2019 at 15:49
  • @pLumo, nah, I like being explicit about the test and exit/return. And the subshell isn't needed for anything else so I just like to avoid it on principle. :P check "$pid" || return 1 would be one alternative, of course. Commented Oct 16, 2019 at 16:58
  • Thank you, putting the PIDs in the array and returning from a function cleans up the control flow a lot. My loop was getting the PIDs from a file with read (left this out of the question for obvious reasons), and I was trying to exit the loop with the success status in $?... much too complicated. Commented Oct 17, 2019 at 9:58
  • @alexis, yeah, getting a sane status out of a loop is hard since return just sets the exit status to zero... But you could wrap the while read... inside a function too and bypass the whole array. That is, if you only need the list of PIDs once. Commented Oct 17, 2019 at 10:38
  • 1
    Well it didn't quite work, hence this question! :-D Commented Oct 17, 2019 at 19:59
4

You could put it in a subshell with exit-on-error ( -e ):

pids=(1025 3425 6474)
(
    set -e
    for pid in "${pids[@]}"; do
        check "$pid"
    done
)
echo $?

Alternatively, you can use || exit 1 instead of set -e:

pids=(1025 3425 6474)
(
    for pid in "${pids[@]}"; do
        check "$pid" || exit 1
    done
)
echo $?
2

Maybe:

parallel -j0 check ::: $pid1 $pid2 $pidN &&
  echo all succeeded
parallel -j0  '! check' ::: $pid1 $pid2 $pidN &&
  echo all failed
parallel -j0 --halt soon,success=1 check ::: $pid1 $pid2 $pidN &&
  echo one succeeded
parallel -j0 --halt soon,fail=1 check ::: $pid1 $pid2 $pidN ||
  echo one failed

It will run the checks in parallel. Replace soon with now if you want running checks to be killed as soon as we know the result.

If you have the PIDs as output from a pipe (one per line):

pid_generator | parallel -j0 check && echo all succeeded

parallel gives one value to check and runs as many check as possible in parallel (-j0).

If the server does not have parallel installed, run this on a machine that has parallel installed:

parallel --embed > new_script

Edit new_script (the last 5 lines) and copy the script to the server.

9
  • And what about the "indeterminate" input? And what if check takes only one argument? Commented Oct 17, 2019 at 2:29
  • 1
    Wow, parallel. I'd probably go for this solution if I was already using parallel, but in this case a pure-bash approach is better (don't want extra dependencies on the target server...) Commented Oct 17, 2019 at 10:01
  • @alexis Try parallel --embed for that. Commented Oct 17, 2019 at 11:44
  • @rastafile check is only called with one input. You can put as many $pids as you want. Commented Oct 17, 2019 at 11:46
  • @OleTange I do not know how many PIDs there are. Question of not knowing, not of not being able to put enough. "one input" is too monolithic. An input stream that finishes or breaks is more flexible. Up to the user to control the "finishing" (error or EOF). Your solution actually needs a helper loop or extension or xargs. Commented Oct 17, 2019 at 11:58
0

A simple function could test each argument; if any fail, return from the function with a non-zero code:

#!/bin/sh

testall() {
  for test
  do
    sh -c "$test" || return 1
  done
}

With some example tests:

$ testall "echo 1" "echo 2" "false" && echo all OK
1
2
$ testall "echo 1" "echo 2" "echo 3" && echo all OK
1
2
3
all OK
$ testall && echo all OK
all OK

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.