Bash for loop: list form, C-style `(( ))`, arrays, break, and continue

Tech reviewed: Deepak Prasad
Bash for loop: list form, C-style `(( ))`, arrays, break, and continue

A bash for loop (the same idea as for loop bash or for loop in bash on any Linux box) runs a block once per item: numbers you list out, words from a command, files that match a glob, or entries from an array. Bash actually offers two shapes: the familiar for name in words … list form, and the C-style for (( … )) counter you reach for when the length is numeric. The rest of this page is the syntax I use in real bash script for loop work, with quoting called out because half the bugs in loops are really word-splitting surprises.

Tested all the commands and code from this article on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37.


List form: for name in …; do …; done

The loop variable takes each word produced after expansion:

bash
for num in 1 2 3 4 5 6; do
  echo "Found Element: $num"
done

Output:

text
Found Element: 1
Found Element: 2
Found Element: 3
Found Element: 4
Found Element: 5
Found Element: 6

Strings work the same way:

bash
for string in STRING1 STRING2 STRING3; do
  echo "Found Element: $string"
done

Output:

text
Found Element: STRING1
Found Element: STRING2
Found Element: STRING3

Brace expansion is handy for ranges without typing every number:

bash
for n in {1..3}; do echo "n=$n"; done

Output:

text
n=1
n=2
n=3

For larger numeric ranges, seq is fine: for i in $(seq 1 10); do … (quote the command substitution if filenames could ever appear—usually not for seq).


Arrays vs one long string

A string with spaces is still one word unless you expand an array without quotes the wrong way. Compare:

bash
VAR=(My name is Deepak)
echo "Length array: ${#VAR[@]}"

VAR2="My name is Deepak"
echo "Length string as single element: ${#VAR2[@]}"

Output:

text
Length array: 4
Length string as single element: 1

Iterate an array safely—always quote "${VAR[@]}" so elements with spaces stay intact:

bash
VAR=(My name is Deepak)

for word in "${VAR[@]}"; do
  echo "word: $word"
done

If you need to build an array from text, see split string into array.


C-style for (( )) (counter loops)

When the job is numeric, for (( … )) reads like C and avoids building a word list:

bash
for (( i = 0; i <= 5; i++ )); do
  echo "Iteration $i"
done

Output:

text
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5

Two counters in one header is legal when you need coupled state:

bash
for (( i = 1, j = 5; i <= j; i++, j-- )); do
  echo "Iteration for i=$i j=$j"
done

Output:

text
Iteration for i=1 j=5
Iteration for i=2 j=4
Iteration for i=3 j=3

Pair this with normal branching from bash if else when each step depends on checks.


continue and break

continue skips the rest of the body and moves to the next value; break leaves the whole loop.

Without continue, the “skip” message still falls through:

bash
for (( i = 0; i <= 5; i++ )); do
  if [ "$i" -eq 2 ]; then
    echo "Don't do anything when i=2"
  fi
  echo "Doing something when i=$i"
done

Output (note the duplicate line for i=2):

text
Doing something when i=0
Doing something when i=1
Don't do anything when i=2
Doing something when i=2
Doing something when i=3
Doing something when i=4
Doing something when i=5

With continue:

bash
for (( i = 0; i <= 5; i++ )); do
  if [ "$i" -eq 2 ]; then
    echo "Don't do anything when i=2"
    continue
  fi
  echo "Doing something when i=$i"
done

Output:

text
Doing something when i=0
Doing something when i=1
Don't do anything when i=2
Doing something when i=3
Doing something when i=4
Doing something when i=5

break example:

bash
for (( i = 0; i <= 5; i++ )); do
  if [ "$i" -eq 2 ]; then
    echo "Exiting, match successful"
    break
  fi
  echo "Doing something when i=$i"
done

Output:

text
Doing something when i=0
Doing something when i=1
Exiting, match successful

When the exit condition is time-based or event-based, a while loop is often clearer than forcing a for.


One-liners and globs (skip ls in loops)

One-liner shape:

bash
for int in alpha beta gamma; do echo "Interface: $int"; done

Output:

text
Interface: alpha
Interface: beta
Interface: gamma

Prefer globs or find over parsing ls. Example: print a few network sysctl directory names (your interface list will differ):

bash
for f in /proc/sys/net/ipv4/conf/*; do
  basename "$f"
done | head -5

On this machine that began with:

text
all
br-bf2c2e38fb2f
default
docker0
enp0s3

Always quote "$f" when you pass it to other commands so odd names do not split.


Habits that keep loops boring (in a good way)

  • Quote variables in tests: [ "$i" -eq 2 ], not [ $i -eq 2 ].
  • Quote "${array[@]}" when iterating arrays.
  • Avoid for f in $(find …) when filenames can contain spaces; use find … -print0 | while IFS= read -r -d '' f or an array built with mapfile.
  • Remember unix bash for loop semantics match Bash; POSIX sh lacks (( )) and some array features—check the shebang.

Summary

List form walks words—literals, brace ranges, globs, or "${array[@]}". Arithmetic for ((i=0;i<n;i++)) is for counting. continue skips the rest of an iteration; break exits the loop. Quote tests and array expansions, avoid feeding ls into for, and use while read when find must handle spaces in paths. Those habits cover most day-to-day loop work without surprises.

Further reading:

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, …

  • Red Hat Certified System Administrator in Red Hat OpenStack
  • Certified Kubernetes Application Developer (CKAD)
  • Red Hat Certified Specialist in Ansible Automation
  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security