Bash — The do Keyword

Here’s a short excerpt for the post: > Learn how Bash uses the `do` keyword to mark the beginning of a loop body. Explore how `do` works with `for`, `while`, and `until`, how it pairs with `done`, and how to write clean, readable Bash loops.

Bash — The do Keyword

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:

  • for defines the loop.
  • do begins the loop body.
  • done ends it.

The do keyword isn't useful by itself. It is part of the syntax of Bash's loop constructs, including forwhile, 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 VenusEarth, 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 forwhile, 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.

Read next

Bash — The continue Built-in

Learn how Bash’s `continue` statement skips the rest of the current loop iteration without stopping the loop. Explore practical examples with `for`, `while`, and `until`, filtering files and data, nested loops, and the difference between `continue` and `break`.

Bash — The break Built-in

Learn how Bash’s break statement lets you exit for, while, until, and select loops, including nested loops. Explore break n, practical examples, and how break differs from continue, return, and exit.

Bash — The alias Built-in

Bash aliases let you turn long, frequently used commands into short, memorable shortcuts. Learn how alias works, how to create, inspect, remove, and persist aliases, and when a function or script is a better choice.