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.
test
or[
I'd recommend using arithmetic expansion:while (($?))
… – Konrad Rudolph Mar 14 '21 at 18:50test
or[
but to let thewhile
oruntil
keywords act on the exist-status of the command directly (by executing the command) instead of on$?
. – Kusalananda Mar 14 '21 at 19:02[
/test
is 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 thatbash
provides. – Kusalananda Mar 14 '21 at 19:34