Bash For Loop: Syntax and Examples

By 

Updated on

10 min read

Bash for loop syntax examples in a terminal

Loops are a fundamental concept in programming languages. They allow you to execute a series of commands repeatedly until a specific condition is met.

In scripting languages like Bash, loops are especially useful for automating repetitive tasks, eliminating the need to duplicate code.

There are three basic loop constructs in Bash scripting: for loop, while loop , and until loop .

This guide focuses on the for loop in Bash, including its syntax variations, nesting, flow-control statements like break and continue, and practical examples.

The Standard Bash for Loop

The for loop iterates over a list of items and performs the given set of commands.

The basic Bash for loop syntax is:

txt
for item in [LIST]
do
  [COMMANDS]
done

The [LIST] can be a series of strings separated by spaces, a range of numbers, command output, an array, command-line arguments, and so on.

Loop Over Strings

The example below shows a loop that iterates over a list of strings. The loop operates on each item in the list, and the variable element stores the item that the loop is currently processing.

sh
for element in Hydrogen Helium Lithium Beryllium
do
  echo "Element: $element"
done
output
Element: Hydrogen
Element: Helium
Element: Lithium
Element: Beryllium

Loop Over a Range

You can use the sequence expression to specify a range of numbers or characters by defining the start and end point of the range. The sequence expression takes the following form:

txt
{START..END}

Here is an example loop that iterates through all numbers from 0 to 3:

sh
for i in {0..3}
do
  echo "Number: $i"
done
output
Number: 0
Number: 1
Number: 2
Number: 3

Bash also supports specifying an increment when using ranges:

txt
{START..END..INCREMENT}

Here is an example showing how to increment by 5:

sh
for i in {0..20..5}
do
  echo "Number: $i"
done
output
Number: 0
Number: 5
Number: 10
Number: 15
Number: 20

Loop Over Array Elements

You can use the for loop to iterate over the elements of a Bash array .

In the example below, we define an array named BOOKS and iterate over each element of the array.

sh
BOOKS=('In Search of Lost Time' 'Don Quixote' 'Ulysses' 'The Great Gatsby')

for book in "${BOOKS[@]}"; do
  echo "Book: $book"
done
output
Book: In Search of Lost Time
Book: Don Quixote
Book: Ulysses
Book: The Great Gatsby

Loop Over Files

A glob pattern in the list makes the loop iterate over matching pathnames in the current directory. Bash keeps each match as a separate item even when a name contains spaces. By default, * does not match names that start with a dot.

The example below enables nullglob and loops over non-hidden pathnames that end in .txt:

sh
shopt -s nullglob

for file in *.txt
do
  echo "File: $file"
done
output
File: report.txt
File: sales data.txt

Notice that sales data.txt arrives as a single item rather than two. Quoting the variable as "$file" when you pass it to a command keeps the name intact from there on. With nullglob enabled, an unmatched *.txt pattern expands to nothing instead of being passed to the loop as a literal string.

Loop Over Command Output

The list can also be built from the output of a command. With an unquoted $(command) substitution, Bash removes trailing newlines, splits the result using IFS, and then applies filename expansion to the resulting words.

The loop below reads the account names from /etc/passwd and prints one line per account:

sh
for user in $(cut -d: -f1 /etc/passwd)
do
  echo "User: $user"
done

That processing is also what makes this form fragile. With the default IFS, Bash splits the output on spaces, tabs, and newlines, so any value that contains a space is torn into separate items:

sh
for name in $(printf 'report.txt\nsales data.txt\n')
do
  echo "Item: $name"
done
output
Item: report.txt
Item: sales
Item: data.txt

The command printed two filenames, but the loop received three items. Wildcard characters in the output can also expand against files in the current directory. Use a glob when you are iterating over files, and a while loop with read when you need to process output line by line .

The C-style for Loop

Bash also supports a C-style for loop syntax:

txt
for ((INITIALIZATION; TEST; STEP))
do
  [COMMANDS]
done

The INITIALIZATION part is executed only once when the loop starts. Then, the TEST part is evaluated using comparison operators . If it is false, the loop is terminated. If the TEST is true, commands inside the body of the for loop are executed, and the STEP part is updated.

In the following example, the loop starts by initializing i = 0 and, before each iteration, checks whether i ≤ 10. If true, it prints the current value of i and increments i by 1 (i++). Otherwise, the loop terminates.

sh
for ((i = 0 ; i <= 10 ; i++)); do
  echo "Counter: $i"
done

The loop iterates 11 times and produces the following output:

output
Counter: 0
Counter: 1
Counter: 2
...
Counter: 8
Counter: 9
Counter: 10

Nested Loops

A nested loop is a loop inside another loop. You can nest loops to any depth.

In a nested for loop, the inner loop runs a complete cycle of its iterations for every outer loop iteration.

Here is an example that copies three files one by one to three servers:

sh
for f in file_{1..3}; do
  for j in server_{1..3}; do
    echo "Copying file $f to server $j"
    # command to copy files
  done
done
output
Copying file file_1 to server server_1
Copying file file_1 to server server_2
Copying file file_1 to server server_3
Copying file file_2 to server server_1
Copying file file_2 to server server_2
Copying file file_2 to server server_3
Copying file file_3 to server server_1
Copying file file_3 to server server_2
Copying file file_3 to server server_3

Controlling Loop Flow: break and continue

The break and continue statements are used to control the execution of a for loop.

break: Terminate the Loop

The break statement terminates the current loop and passes control to the statement that follows it. It is generally used to exit the loop when a specific condition is met.

In the following example, the if statement terminates the loop once the current item is equal to Lithium:

sh
for element in Hydrogen Helium Lithium Beryllium; do
  if [[ "$element" == 'Lithium' ]]; then
    break
  fi
  echo "Element: $element"
done

echo 'All Done!'
output
Element: Hydrogen
Element: Helium
All Done!

continue: Skip to the Next Iteration

The continue statement exits the current iteration and passes control to the next one.

In the following example, when the current item equals 2, the continue statement skips the echo and moves to the next iteration:

sh
for i in {1..5}; do
  if [[ "$i" == '2' ]]; then
    continue
  fi
  echo "Number: $i"
done
output
Number: 1
Number: 3
Number: 4
Number: 5

Real-World Examples

Warning
The next two examples rename files. Test them on copies first. The -n option prevents mv from overwriting an existing destination.

Rename Files with Spaces in the Name

The following example renames non-hidden regular files in the current directory that contain a space, replacing spaces with underscores:

sh
shopt -s nullglob

for file in *\ *; do
  [[ -f "$file" ]] || continue
  mv -n -- "$file" "${file// /_}"
done
  • *\ * matches non-hidden names that contain a space.
  • ${file// /_} uses shell parameter expansion to replace every space with an underscore.
  • [[ -f "$file" ]] continues only when the match resolves to a regular file.
  • mv -n -- keeps an existing destination file unchanged and prevents filenames that start with a dash from being read as options.

Change File Extension

The following example renames non-hidden regular files ending in .jpeg in the current directory, replacing the extension with .jpg:

sh
shopt -s nullglob

for file in *.jpeg; do
  [[ -f "$file" ]] || continue
  mv -n -- "$file" "${file%.jpeg}.jpg"
done
  • *.jpeg matches non-hidden names that end in .jpeg.
  • ${file%.jpeg} strips the .jpeg suffix using shell parameter expansion, and .jpg is appended to form the new name.
  • [[ -f "$file" ]] continues only when the match resolves to a regular file.
  • mv -n -- keeps an existing .jpg file unchanged and prevents filenames that start with a dash from being read as options.

Restart Multiple Docker Containers

Here is an example that restarts a list of containers after deploying new code:

sh
for container in web db cache redis; do
  docker restart "$container" && echo "Restarted $container"
done

Containers are passed to the loop as a list of strings.

Troubleshooting

Loop splits a filename or string with spaces into multiple items
With the default IFS, Bash performs word splitting on spaces, tabs, and newlines. Always quote variables and array expansions: use "${ARRAY[@]}" instead of ${ARRAY[@]}, and "$file" instead of $file.

Glob pattern in the list expands unexpectedly
When a glob pattern like *.jpeg matches no files, Bash passes the literal string *.jpeg to the loop variable. Add shopt -s nullglob before the loop to make unmatched globs expand to nothing instead.

C-style loop does not behave like expected with [[ ]]
The C-style loop uses arithmetic evaluation: comparisons inside (( )) use <, >, and == directly. Do not mix [[ ]] test syntax inside the loop initializer or condition.

Range with a variable does not expand
Brace expansion happens before variable substitution, so {1..$N} does not work. Use a C-style loop or seq instead: for i in $(seq 1 "$N"); do.

Quick Reference

For a printable quick reference, see the Bash cheatsheet .

SyntaxDescription
for item in a b c; do ... doneLoop over a list of strings
for i in {1..10}; do ... doneLoop over a numeric range
for i in {0..20..5}; do ... doneLoop over a range with increment
for item in "${ARRAY[@]}"; do ... doneLoop over array elements
for file in *.txt; do ... doneLoop over matching non-hidden pathnames; enable nullglob first
for x in $(command); do ... doneSplit command output on whitespace; unsafe for arbitrary text
for ((i=0; i<10; i++)); do ... doneC-style loop
breakExit the loop immediately
continueSkip to the next iteration

FAQ

What is the difference between the standard for loop and the C-style for loop?
The standard for loop iterates over a list of items (strings, array elements, a range). The C-style for loop uses arithmetic initialization, condition, and step expressions, so it is better suited for counter-based iteration where you control the start, end, and step explicitly.

How do I loop over lines in a file?
Use a while loop with read instead: while IFS= read -r line; do echo "$line"; done < file.txt. A for loop is not suited for line-by-line file reading because it splits on whitespace, not newlines.

Can I use break or continue in nested loops?
Yes. By default, break and continue affect only the innermost loop. To exit an outer loop from an inner one, use a numeric argument: break 2 exits two levels of nesting.

How do I loop over command-line arguments?
Use for arg in "$@"; do to iterate over all positional parameters passed to the script.

Conclusion

The Bash for loop iterates over a list of items or runs for a defined number of iterations using C-style syntax. Combined with break, continue, nesting, and parameter expansion, it enables elegant solutions to many automation tasks.

To learn all the ways to run your scripts, see How to Run a Bash Script .

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page