Bash loop syntax can look unusual if you're coming from languages such as C, Java, or Python.
Instead of using braces to mark the body of a loop, Bash uses the keywords do and done:
for item in one two three
do
echo "$item"
done
Here:
fordefines the loop.dobegins the loop body.doneends it.
The do keyword isn't useful by itself. It is part of the syntax of Bash's loop constructs, including for, while, and until.
A Simple Example
Consider the original example:
#!/bin/bash
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
echo "$planet"
done
The output is:
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
The loop iterates over the values assigned to planet.
Everything between do and done is executed once for each value:
do
echo "$planet"
done
So when planet is Mercury, Bash executes:
echo "Mercury"
Then it repeats the same body for Venus, Earth, and so on.
do and done
The easiest way to understand the syntax is to treat do and done as a pair:
do
# loop body
done
They define the boundaries of the commands that belong to the loop.
For example:
for number in 1 2 3
do
echo "Number: $number"
echo "Another command"
done
Both commands inside the block are executed for every value.
Output:
Number: 1
Another command
Number: 2
Another command
Number: 3
Another command
The number of iterations and the conditions that control the loop are determined by for, while, or until, not by do.
do With for
The most common form is:
for item in values
do
commands
done
For example:
for number in 1 2 3 4 5
do
echo "Number: $number"
done
You can put any number of commands inside the loop:
for number in 1 2 3
do
echo "Processing $number"
result=$((number * 10))
echo "Result: $result"
done
Output:
Processing 1
Result: 10
Processing 2
Result: 20
Processing 3
Result: 30
The do keyword simply separates the loop definition from the commands that should be repeated.
do With while
The same structure is used by while:
counter=1
while [ "$counter" -le 5 ]
do
echo "Counter: $counter"
counter=$((counter + 1))
done
The while condition determines whether another iteration should run.
The body remains enclosed by:
do
...
done
The output is:
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
do With until
until uses the same structure:
counter=1
until [ "$counter" -gt 5 ]
do
echo "Counter: $counter"
counter=$((counter + 1))
done
The difference is in how the loop condition is evaluated; the syntax of the loop body doesn't change.
This gives Bash loops a consistent structure:
do
commands
done
do on the Same Line
do doesn't have to be on its own line.
This:
for item in one two three
do
echo "$item"
done
can also be written as:
for item in one two three; do
echo "$item"
done
The semicolon separates the loop definition from do.
The same applies to while:
while [ "$counter" -lt 10 ]; do
echo "$counter"
counter=$((counter + 1))
done
Both styles are valid. Keeping do on its own line can make longer loops easier to scan, while putting it on the same line is common in compact scripts.
Why Does Bash Use do?
Many programming languages use braces to define blocks:
for (...)
{
commands
}
Bash uses keywords instead:
for ...
do
commands
done
This approach is also used by other Bash compound commands.
For example, conditional code uses:
if condition
then
commands
fi
A loop uses:
for ...
do
commands
done
So do is part of Bash's keyword-based syntax for defining a command block.
Multiple Commands in a Loop
A loop body can contain a single command:
for file in *.txt
do
echo "$file"
done
or many commands:
for file in *.txt
do
echo "Processing $file"
size=$(wc -c < "$file")
echo "Size: $size bytes"
cp "$file" /backup/
echo "Backup completed"
done
Every command between do and done is executed for each iteration.
Indentation isn't required by Bash, but it makes the structure much easier to read:
for file in *.txt
do
echo "Processing $file"
if [ -f "$file" ]; then
wc -l "$file"
fi
done
The indentation is for humans; do and done define the actual structure.
Using break and continue
Loop-control commands can be used inside the do/done block.
For example:
for number in {1..10}
do
if (( number == 5 )); then
continue
fi
if (( number == 8 )); then
break
fi
echo "$number"
done
Here, continue skips the current iteration and break terminates the loop.
The important point is that both operate within the loop established by the surrounding for ... do ... done structure.
Nested Loops
A loop can contain another loop, and each loop has its own do and done:
for outer in 1 2 3
do
echo "Outer: $outer"
for inner in A B C
do
echo " Inner: $inner"
done
done
Output:
Outer: 1
Inner: A
Inner: B
Inner: C
Outer: 2
Inner: A
Inner: B
Inner: C
Outer: 3
Inner: A
Inner: B
Inner: C
The indentation makes it clear which done belongs to which do.
For more complicated nested loops, keeping each pair aligned is especially helpful:
for outer in ...
do
for inner in ...
do
commands
done
done
do Inside a Function
Functions have their own syntax and don't use do:
process_file()
{
echo "Processing file"
}
A loop inside a function does use it:
process_files()
{
for file in *.txt
do
echo "Processing $file"
done
}
Here the function body is enclosed in { ... }, while the loop body is enclosed by do ... done.
A Practical Example
Let's use the same structure for something a little more realistic:
#!/bin/bash
for file in *.log
do
echo "Checking $file"
lines=$(wc -l < "$file")
echo "$file contains $lines lines"
done
If the directory contains:
application.log
database.log
server.log
the script might produce:
Checking application.log
application.log contains 152 lines
Checking database.log
database.log contains 843 lines
Checking server.log
server.log contains 421 lines
The for statement determines which files are visited.
The commands between do and done perform the work for each file.
What Happens If do Is Missing?
Consider:
for item in one two three
echo "$item"
done
Bash cannot parse this as a valid for loop because the loop body wasn't introduced with do.
Likewise, this is incomplete:
for item in one two three
do
echo "$item"
The closing done is missing.
When Bash reads a script, it expects the compound command to be properly closed.
These errors are easy to avoid if you keep the standard structure visible:
for item in ...
do
commands
done
do Is Not an External Command
do is part of Bash's syntax. Bash doesn't launch a separate executable when it encounters it.
You can see Bash's reserved words with:
compgen -A reserved
do is among them.
This is different from a command such as:
ls
which is normally an external executable, or:
echo
which Bash provides as a built-in command.
A useful distinction is:
do → shell syntax
echo → Bash built-in
ls → usually external command
The Common Loop Structure
Once you recognize the role of do, the three basic Bash loop forms become easy to compare.
for
for item in values
do
commands
done
while
while condition
do
commands
done
until
until condition
do
commands
done
Only the first part changes.
The body consistently appears between do and done.
Quick Reference
for item in values
do
commands
done
while condition
do
commands
done
until condition
do
commands
done
You can also use the compact form:
for item in values; do
commands
done
And nested loops follow the same pattern:
for outer in ...
do
for inner in ...
do
commands
done
done
Getting Help
do is a shell keyword rather than a standalone command, so the most useful documentation comes from the loop constructs that use it:
help for
help while
help until
You can also consult the Bash manual's sections on shell compound commands and loops.
Conclusion
do has one simple job: it marks the beginning of a loop's command body.
The complete structure is:
for ...
do
commands
done
The same pattern applies to while and until.
Once you recognize do ... done as the standard boundary of a Bash loop, the syntax becomes much easier to read:
loop definition
↓
do
↓
loop body
↓
done
It's a small piece of Bash syntax, but you'll encounter it in almost every non-trivial shell script that uses loops.