The while and until keywords takes a command as their argument. If the command returns a zero exit-status, signalling "success", while would run the commands in the body of the loop, while until would not run the commands in the body of the loop.
Your code seems to use while and until with $?, the exit-status of the most recent command, as a command. This will not work, unless you have commands with integers as names on the system. What would work is to use the test command to compare it against 0, for example, with
until test "$?" -eq 0; do some-command; done
or, using the short form of test,
until [ "$?" -eq 0 ]; do some-command; done
The issue now is obviously that $? may well be zero when entering the loop, preventing the loop from running even once. It would make more sense to let the command itself give its exit-status directly to either while or until (unless you want to initialize $? to a non-zero value by calling e.g. false just before the loop, which would look strange).
Using standard POSIX (though not Bourne, not that it matters much these days) shell syntax:
while ! some-command; do continue; done
or (both Bourne and POSIX),
until some-command; do continue; done
Both of these runs some-command until it returns a zero exit-status ("success"). In each iteration, it executes the continue keyword inside the loop body. Executing continue skips ahead to the next iteration of the loop immediately, but you could replace it with e.g. sleep 5 to have a short delay in each iteration, or with : which does nothing.
Another variant would be to have an infinite loop that you exit when your command succeeds:
while true; do some-command && break; done
or, slightly more verbose, and with added air for readability,
while true; do
if some-command; then
break
fi
done
The only time you actually need to use $? is when you are required to keep track of this value for a while, as in
somefunction () {
grep -q 'pattern' file
ret=$?
if [ "$ret" -ne 0 ]; then
printf 'grep failed with exit code %s\n' "$ret"
fi >&2
return "$ret"
}
In the code above, the exit-status in $? is reset by the test [ "$ret" -ne 0 ] and we would not be able to print its value nor return it from the function without storing it in a separate variable.
Another thing to point out is that you seem to be using a string, $command (unquoted), as your command. It would be much better to use an array if you want to run a command stored in a variable:
thecommand=( awk -F ',' 'BEGIN { "uptime" | getline; split($4,a,": "); if (a[2] > 5) exit 1 }' )
while ! "${thecommand[@]}"; do sleep 5; done
(This example code sleeps in intervals of five seconds until the 1-minute average system load has gone below 5.)
Note how by using an array and quoting the expansion as "${thecommand[@]}", I can run my command without worrying about how the shell would split the string into words and apply filename globbing to each of those words.
See also How can we run a command stored in a variable? for more info on this aspect of your issue.
testor[I'd recommend using arithmetic expansion:while (($?))… – Konrad Rudolph Mar 14 '21 at 18:50testor[but to let thewhileoruntilkeywords act on the exist-status of the command directly (by executing the command) instead of on$?. – Kusalananda Mar 14 '21 at 19:02[/testis simply a lot more error-prone than the Bash alternatives. That said, the real solution (which your answer contains) of course uses none of these alternatives. – Konrad Rudolph Mar 14 '21 at 19:29test, will also have issues with the special syntactic sugar thatbashprovides. – Kusalananda Mar 14 '21 at 19:34