15

I have the following script that works fine:

!/bin/bash

a=12
while [ $a -gt 10 ]
do
    echo "$a"
    a=$(($a-1))
done
echo "done"

If I add line "echo something" above "do", I expect to see a syntax error in that line. It seems that [ $a -gt 10 ] is bypassed, and it becomes an infininte loop. How could that happen?

  • I guess one use-case of that is for do-while loops like in C, to check the condition after each iteration instead of before. – JoL Dec 5 '19 at 17:10
29

From the bash manual:

while

The syntax of the while command is:

while test-commands; do consequent-commands;
done
Execute consequent-commands as long as test-commands has an exit status of zero. The return status is the exit status of the last command executed in consequent-commands, or zero if none was executed.

Note: test-commands, plural. You can use multiple commands in the test, and so this is a perfectly valid loop, with the list of commands [ $a -gt 10 ]; echo "$a" as the test:

while [ $a -gt 10 ]
echo "$a"
do
   a=$(($a-1))
done

While the command [ $a -gt 10 ] may or may not fail, the echo will (almost) always succeed (unless it couldn't write the text, or some other error happened), so the final exit status of the test commands will always be success, and the loop will always be run.

  • 1
    Was this the case historically? With original UNIX v7 Bourne shell? Because if so, this would be revelatory - it would justify the choice of even having an additional do keyword in the first place, which otherwise seems like a weirdly redundant syntax choice. – mtraceur Dec 6 '19 at 22:26
  • 1
    At least in the seventh edition, yes. Here's the manpage for sh: freebsd.org/cgi/… talking about while list [do list;] done - interestingly the do list is marked as optional. – muru Dec 7 '19 at 1:19
  • interestingly indeed, it allows while-done to function as do-while in other languages, – Jasen Dec 7 '19 at 22:57
  • 1
    Worth noting that this is not just bash specific behavior, POSIX Shell Grammar specifies while_clause : While compound_list do_group and compound_list is specified as newline_list term. Tested that with dash on Debian-based system - works as well. – Sergiy Kolodyazhnyy Dec 8 '19 at 8:35
10

From man bash:

while list-1; do list-2; done
The while command continuously executes the list list-2
as long as the last command in the list list-1
returns an exit status of zero.

Which implies that a list could contain several commands, which it does (separated mostly by semicolons or newlines).

So, this works perfectly well:

#!/bin/bash

a=12

while 
    echo something
    echo "a before test =  $a"
    [ a -gt 10 ]
do
    echo "a after test =  $a"
    a=$(($a-1))
done
echo "done"

If the last command before the do is echo the exit code that the do receive is always true (0) and the loop becomes infinite.

4

As an example of this being taken to extremes, /usr/bin/tzselect typically has about 70 lines of code between the while and the do of the main loop which includes case statements and command substitution, and a single line between the do and the done.

  • ...and the single line is : (NOOP). – Alex Hajnal Dec 5 '19 at 10:33
  • 1
    @AlexHajnal Interesting, in mine it's coord=. – JoL Dec 5 '19 at 17:08
4

What the other answers here imply but don't explicitly say is that [ is a built-in command, not a syntactical part of the while statement.

Try typing help [ at your command line:

[: [ arg... ]
Evaluate conditional expression.

This is a synonym for the "test" builtin, but the last argument must
be a literal ], to match the opening [.

So your script is exactly as:

!/bin/bash
a=12
while test $a -gt 10
do
  echo "$a"
  a=$(($a-1))
done
echo "done"

Which you change to:

!/bin/bash
a=12
while test $a -gt 10; echo something
do
  echo "$a"
  a=$(($a-1))
done
echo "done"
2

(as a note to complement other answers).

Using several commands in the condition list is often used to implement a loop similar to C's do { blah; blah; } while (condition), that is where the condition is checked at the end of the loop so the code in the loop is run at least once.

In sh, you'd do it as:

while
  blah
  blah
  condition
do
  continue # or :
done

Though other approaches are possible like:

while true; do
  blah
  blah
  condition || break
done

Or:

continue=true
while "$continue"; do
  blah
  blah
  condition || continue=false
done
end=false
until "$end"; do
  blah
  blah
  condition || end=true
done

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.