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?

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

  • 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). – pLumo Oct 16 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. – ilkkachu Oct 16 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. – alexis Oct 17 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. – ilkkachu Oct 17 at 10:38
  • 1
    Well it didn't quite work, hence this question! :-D – alexis Oct 17 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.

  • And what about the "indeterminate" input? And what if check takes only one argument? – rastafile Oct 17 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...) – alexis Oct 17 at 10:01
  • @alexis Try parallel --embed for that. – Ole Tange Oct 17 at 11:44
  • @rastafile check is only called with one input. You can put as many $pids as you want. – Ole Tange Oct 17 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. – rastafile Oct 17 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
-2
$  cat pids | parallel -j0 cat && echo OK!
cat: 23: No such file or directory
cat: 45: No such file or directory
cat: 67: No such file or directory
cat: 89: No such file or directory
cat: '': No such file or directory

$  cat pids | parallel -j0 echo && echo OK!
23
45
67
89

OK!

parallel does not stop at the first error


$   cat pids | while read pid; do cat \"$pid\" || break; done
cat: '"23"': No such file or directory

$   cat pids | while read pid; do echo \"$pid\" || break; done
"23"
"45"
"67"
"89"
""

bash only: break means break

(Empty last line and return status are implemented differently)


Or do you mean this:

cat pids | while read pid; do cat \"$pid\" || break; done

It does not derive a status, I admit, but that follows a bit from the lacking specifications.

When I run with echo in the loop it just puts quotes around the lines from file "pids" until EOF. With cat AND ||break it stops with an error "no file". Without break it prints many "no file" errors. So it works? Now I just added && echo OK, because that command does indeed return 0 or 1, corresponding to the error messages.

The "pids" file contains the pids of course. Piping this file simulates a STREAM of lines with single pids that goes on until it abruptly stops. Indeterminate, just as you say.


Sorry but this Q is absolutely unclear. Different tests on different PIDs indeterminately running.

If you have just any tests then the loop is fine.

If you mean it halfways serious then you have to organize your alogrithm. Break down the logic into smaller steps. Make it easy for you and the CPU.

Your if a then if b then if c then if d then, well, then ALL OK might be borderline OK for one extreme situation. I don't think you have one where this approach is useful.

  • @alexis only here you see a truly indeterminate input stream. What is the syntax of your check? – rastafile Oct 16 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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