Bash for Loops in Linux
Learn Bash for loop syntax, list iteration, loop variables, globbing, quoting, dynamic lists, practical examples, and troubleshooting.
A Bash for loop is a control structure that repeats a command block once for each item in a supplied list. Loops are useful when the same operation must be performed on several files, values, names, or other items.
This lesson assumes basic Linux command-line navigation, Bash commands, shell variables, command arguments, whitespace, and simple output commands such as echo or printf.
Why Use Loops in Shell Scripts?
A loop repeats a group of commands. Instead of writing the same command separately for every item, you write the command block once and let Bash repeat it.
Different loop forms control repetition in different ways:
- A for loop processes items from a supplied collection.
- A while loop repeats while a test command succeeds.
- An until loop repeats until a test command succeeds.
Use a for loop when you know the values or collection that should be processed. Use while or until when continuation depends primarily on a changing condition.
How a Bash for Loop Works
A Bash for loop processes list items sequentially. The loop body runs one time for each item produced by the list. Therefore, the normal number of iterations equals the number of items in that list.
During each iteration, which means one pass through the loop body, Bash assigns the next item to the loop variable and runs the commands between do and done.
Basic for Loop Syntax
for variable in list; do
commands
done
The semicolon separates the list from do when they appear on the same line. The equivalent multiline form places do on its own line:
for variable in list
do
commands
done
Indentation is recommended because it makes the loop body easier to read, but Bash does not require indentation.
| Part | Purpose | Example |
|---|---|---|
for | Begins the loop declaration. | for |
| Loop variable | A shell variable that receives the current item. | file |
in | Introduces the list of items to process. | in |
| List | The sequence of values supplied to the loop. | one two three |
do | Marks the beginning of the repeated commands. | do |
| Loop body commands | The commands executed for every list item. | echo "$file" |
done | Marks the end of the loop body. | done |
A compact one-line loop is also valid:
for item in one two three; do echo "$item"; done
Loop Variables and Variable Expansion
The name chosen after for is a normal shell variable. In this example, color receives a different value on each iteration:
for color in red green blue; do
echo "Selected: $color"
done
The dollar sign performs variable expansion, meaning Bash replaces $color with the variable's current value. Braced syntax, such as ${color}, is also valid and can make variable boundaries clearer.
After the loop finishes in the current shell, the loop variable normally retains the final assigned value:
for item in first second third; do
printf '%s\n' "$item"
done
printf 'After the loop: %s\n' "$item"
The final command prints third, because that was the last value assigned to item. A loop running in a separate subshell can have different variable-scope effects.
Literal Lists and Iteration Count
Whitespace separates unquoted list elements. This loop has five values, so its body executes five times:
for num in 2 12 17 104 10003; do
echo "The number is $num."
done
The values look numeric, but Bash supplies them as values to the loop variable. They are not automatically used in arithmetic. A command or arithmetic expression must explicitly perform any numeric calculation.
The items can be words, names, or other strings:
for color in red green blue; do
echo "Selected: $color"
done
Lists Generated by the Shell
A list does not have to be typed entirely by hand. Bash can produce list items through several kinds of expansion:
- Explicit lists:
red green blue - Pathname expansion: a wildcard such as
*.txtbecomes matching pathnames. - Brace expansion:
{1..5}generates1 2 3 4 5. - Command substitution:
$(command)uses command output as input to another command.
Brace Expansion
for day in {1..5}; do
echo "Day $day"
done
Brace expansion is performed by the shell before the loop runs. It generates text values; it does not itself perform arithmetic.
Pathname Expansion with Globs
A glob is a wildcard pattern. For example, *.txt normally expands to each matching text-file pathname before the loop starts:
for file in *.txt; do
printf '%s\n' "$file"
done
Bash passes each matched pathname as an individual loop item. Always quote a filename variable when passing it to a command. Quoting preserves spaces and other characters within the pathname as one argument.
for file in *.log; do
echo "File: $file"
wc -l "$file"
done
In default Bash settings, if no file matches *.txt, the pattern may remain as the literal text *.txt. A script could then accidentally act on that non-existent name. Check for matches before acting, or deliberately configure glob behavior when appropriate. For example, enabling nullglob makes an unmatched pattern expand to no words:
shopt -s nullglob
files=( *.txt )
for file in "${files[@]}"; do
printf '%s\n' "$file"
done
Command Substitution and Word Splitting
Command substitution is written as $(command). It captures command output so the shell can use that output as text:
for item in $(printf '%s\n' one two three); do
echo "$item"
done
Unquoted command substitution is subject to word splitting. Word splitting can break output into separate words wherever the shell finds whitespace. Consequently, this pattern is not suitable for arbitrary filenames or records containing spaces:
for file in $(find . -type f); do
echo "$file"
done
For filenames or records that may contain spaces, newlines, or other special characters, use a line-safe or NUL-safe reading approach rather than relying on unquoted command substitution.
Using Multiple Commands in the Loop Body
The loop body can contain several commands. Every command between do and done runs once for each item:
for file in *.log; do
echo "File: $file"
wc -l "$file"
done
Loops can print items, inspect them, rename them, process their contents, or pass them to other commands. When testing a loop that will modify or delete files, begin with echo or printf so you can verify the generated command arguments first.
for, while, and until Compared
| Loop type | Best used when | Stopping behavior |
|---|---|---|
for | You need to process supplied values or items in a collection. | Normally stops when the list is exhausted. |
while | Repetition should continue while a test succeeds. | Stops when the test command fails. |
until | Repetition should continue until a test succeeds. | Stops when the test command succeeds. |
A normal for loop does not stop because a Boolean condition changed. Its usual stopping point is the exhaustion of its list. Choose while or until when a condition should control continuation.
Common Errors and Troubleshooting
Syntax error near do or done
A separator or newline may be missing before do, or the loop may be missing its matching done. Use a newline before do or a semicolon after the list:
for item in one two; do
echo "$item"
done
Every for loop must have a matching done.
The expected value is missing
If the loop variable is written without expansion, Bash treats its name as ordinary text. Use $variable or ${variable}:
for num in 1 2 3; do
echo "$num"
done
Filenames with spaces become multiple arguments
This usually happens when a filename variable is expanded without quotes. Use quoted expansion, such as cp "$file" destination/ or rm -- "$file". Quoting is especially important for commands that modify or remove files.
The loop runs once with literal *.txt
No pathname matched the glob, and default Bash behavior left the pattern unchanged. Check that matching files exist before acting, or use an intentional glob configuration such as nullglob.
Unexpected splitting from command output
Unquoted command substitution is split on whitespace. Do not use for item in $(command) for arbitrary filenames or records containing spaces. Use a suitable line-oriented or NUL-delimited reading method instead.
Expecting a for loop to stop on a condition
A for loop normally iterates over its list until no items remain. If continuation should depend on a test, use a while or until loop instead.
Key Points
- A Bash
forloop runs a loop body once for each item in a list. - The loop variable receives the current item and can be expanded with
$nameor${name}. - Five list items normally produce five iterations.
- Lists can be explicit or generated through pathname expansion, brace expansion, and command substitution.
- Quote filename variables when using them as command arguments.
- Unquoted command substitution can split filenames and records at whitespace.
- Test file-processing loops with
echoorprintfbefore using commands that change or delete data.