What is the Right Way to do Bash Loops?

Channel: bash
Abstract: # continue execution from a nested bash for loop(loop). [me@linux ~]$ myArray=(a b c)

The loop constructs are in every programming language, including Bash. Loops are an important building block in a shell script which allows to iterate over a section of code. The bash loop constructs include the for loop, while loop, and until loop.

? Sometimes, you may find references to a select loop in bash. It is not part of the bash loop constructs. You can read more about the select-loop in my post How To Create Simple Menu with the Shell Select Loop?

What is a loop construct?

The loop constructs are common programming building blocks that allow us to repeat the execution of a section of code through an iteration.

The four main types of iteration constructs are the count-controlled loops (or definite iteration), the condition-controlled loops (or indefinite iteration), the infinite loops, and the collection-controlled loops.

  • The count-controlled loop repeats the execution of a section of code for a certain number of times. The counting can be upward or downward with varying step size. This loop generally uses a for loop construct.
  • The condition-controlled loop repeats the execution of a section of code until a condition is met. The condition may be tested at the beginning, or the end of the loop. This loop generally uses a while loop construct.
  • The infinite loop repeats the execution of a section of code forever or until an exception arises. This loop often uses a while true loop construct and is sometimes called an endless loop or a forever loop.
  • The collection-controlled loop iterates over all the elements of an array, or all the items of a list. This loop generally uses a foreach loop or a for in loop construct and may not be available in all programming languages.
What are the different Bash loop constructs?

In Bash, the loops are part of the control flow statements. There is three loop constructs available in bash: for-loop, while-loop, and until-loop. All the bash loop constructs have a return status equals to the exit status of the last command executed in the loop, or zero if no command was executed.

The For loop

The bash for loop construct allows for two types of iteration. First, the collection-controlled loop, similar to a foreach construct in some other languages. The second for-loop construct is a count-controlled loop, which is a traditional iteration with a counter and may also be used as an infinite loop.

The collection-crontolled for-loop (foreach or range)

The below syntax is used in a collection-controlled for loop to iterate over a list and execute a set of commands once for each member. It is similar to a foreach statement in other languages and can be used to loop over a range of numbers or words. An example of a collection-controlled loop can be done using the Bash Brace Expansion.

The [ in [ words …] ] is optional in Bash. When not present, the for-loop will use the $@ variable and will iterate over each positional parameters, similar to for name in "$@".

# For-loop syntax (foreach or range)

for name [ [in [words …] ] ; ] do
  commands
done

# Examples

[me@linux ~]$ for os in linux mac windows; do echo "I love ${os}"; done
I love linux
I love mac
I love windows

[me@linux ~]$ for number in {0..100..20}; do echo "This is number ${number}"; done
This is number 0
This is number 20
This is number 40
This is number 60
This is number 80
This is number 100

# Example Script named for-loop-example.sh using the default $@
for x; do echo $x; done
# Example of script execution and output
[me@linux ~]$ bash for-loop-example.sh 1 2 3
1
2
3
The count-controlled for-loop (counter)

The below syntax is used in a count-controlled for loop, i.e. a for-loop counter, which uses a bash arithmetic expansion.

The first expression is evaluated once according to shell arithmetics rules. The second expression is evaluated each repeatedly until it evaluates to zero. Each time the second expression does not evaluate to zero, the command block is executed and the third arithmetic expression is evaluated.

Any of the arithmetic expressions in the for-loop can be omitted. If omitted, it behaves as if the expression evaluates to 1. You can use this construct as a way to create an infinite loop.

# For-loop syntax (counter)

for (( expr1 ; expr2 ; expr3 )) ; do
  commands
done

# Example of for-loop with a counter
[me@linux ~]$ for ((x=0 ; x<5 ; x++)); do echo "\$x equals to $x"; done
$x equals to 0
$x equals to 1
$x equals to 2
$x equals to 3
$x equals to 4

# Endless loop example
[me@linux ~]$ for (( ; ; )); do echo "infinite loop"; done
infinite loop
...

# Endless loop example with an endless counter increment
[me@linux ~]$ for (( x=0 ; ; x++ )); do echo "\$x=$x"; done
$x=0
$x=1
$x=2
...
The While loop

The bash while-loop construct can be used to create a condition-controlled loop using a bash conditional expression, a bash arithmetic expansion, or based on the exit status of any command. The loop will execute as long as the test command has an exit code status of zero.

This means that you can also use the while-loop construct as a way to do an infinite loop when combined with the true command or the bash null command (i.e. by using the colon character :).

# While-loop syntax
while test-commands; do
  consequent-commands;
done

# While loop example with a bash arithmetic condition
[me@linux ~]$ x=3; while (( x > 0 )); do echo $((x--)); done
3
2
1

# Endless loop example
[me@linux ~]$ while : ; do echo "infinite loop"; done
infinite loop
...

Using a while-loop is generally the recommended way in Bash for iterating over each line of a file or stream. See the FAQ How To Loop Over Each Lines Of A File? for an example.

The Until loop

The bash until-loop construct can be used to create a condition-controlled loop using a bash conditional expression, a bash arithmetic expansion, or based on the exit status of any command.

The until-loop is similar to the while-loop except that it will execute as long as the test command has an exit code status which is not zero. This means that you can also use the until-loop construct as a way to do an infinite loop when combined with the false command.

# until-loop syntax
until test-commands; do
  consequent-commands;
done

# Until loop example with a bash arithmetic condition
[me@linux ~]$ x=3; until (( x <= 0 )); do echo $((x--)); done
3
2
1

# Endless loop example
[me@linux ~]$ until false; do echo "infinite loop"; done
infinite loop
...
How to interrupt a loop in Bash?

You can control the bash loop execution by using the special shell keywords break, to interrupt a loop execution, and continue, to skip the remaining command inside the enclosing loop and continue the rest of the loop iteration.

Both the break and continue commands can be used inside a for, while, and until loop and can take an optional parameter [n].

The Bash Break Builtin

The break command syntax is break [n] and can be used in any bash loop construct. The [n] parameter is optional and allows you to specify which level of enclosing loop to exit, the default value is 1. The return status is zero, unless n is not greater or equal to 1.

[me@linux ~]$ for (( x=0 ; ; x++ )); do { echo "\$x=$x"; break; } done
$x=0
[me@linux ~]$ echo $?
0

[me@linux ~]$ for (( x=0 ; ; x++ )); do { echo "\$x=$x"; break -1; } done
$x=0
bash: break: -1: loop count out of range
[me@linux ~]$ echo $?
1

# break execution from a nested bash for loop, with n=1
for (( x=0 ; ; x++ )); do {
  for (( y=0 ; ; y++ )); do {
    echo -n "\$y=$y "; break;
  } done
  echo "\$x=$x";
} done
# Output
$y=0 $x=0
$y=0 $x=1
$y=0 $x=2
...

# break execution from a nested bash for loop, with n=2
for (( x=0 ; ; x++ )); do {
  for (( y=0 ; ; y++ )); do {
    echo -n "\$y=$y "; break 2;
  } done
  echo "\$x=$x";
} done
# Output
$y=0
The Bash Continue Builtin

The continue command syntax is continue [n] and can be used in any bash loop construct. The [n] parameter is optional and allows you to specify which level of enclosing loop to resume from, the default value is 1. The return status is zero, unless n is not greater or equal to 1.

[me@linux ~]$ for (( x=0 ; ; x++ )); do { echo "\$x=$x"; continue; } done
$x=0
$x=1
$x=2
...
[me@linux ~]$ echo $?
0

[me@linux ~]$ for (( x=0 ; ; x++ )); do { echo "\$x=$x"; continue -1; } done
$x=0
bash: continue: -1: loop count out of range
[me@linux ~]$ echo $?
1

# continue execution from a nested bash for loop, with n=1
for (( x=0 ; x < 2 ; x++ )); do {
  echo "\$x=$x";
  for (( y=0 ; y < 2 ; y++ )); do {
    echo " \$y=$y"; continue;
  } done
} done
# Output
$x=0
 $y=0
 $y=1
$x=1
 $y=0
 $y=1

# continue execution from a nested bash for loop, with n=2
for (( x=0 ; x < 2 ; x++ )); do {
  echo "\$x=$x";
  for (( y=0 ; y < 2 ; y++ )); do {
    echo " \$y=$y"; continue 2;
  } done
} done
# Output
$x=0
 $y=0
$x=1
 $y=0
Detailed Examples & FAQHow to do a foreach loop in bash?

There is no foreach keyword in bash since one of the main syntaxes of a standard bash for loop allows for iterating over each member of a list. See The For Loop section of this post.

How to do a do-while loop in bash?

There is no do-while loop in bash. To execute a command first then run the loop, you must either execute the command once before the loop or use an infinite loop with a break condition.

# Example do-while using a for-loop
function doThis() {
  echo "do-while example"
}
doThis
for (( x=0; x<2; x++ )); do doThis; done

# Output
do-while example
do-while example
do-while example

# Example do-while using a while-loop
x=0
while :; do
  echo "do-while example"
  if (( x>=2 )); then break; fi
  x+=1
done

# Output
do-while example
do-while example
do-while example
How to create an infinite loop in bash?

There are few ways to create an infinite loop in bash, sometimes called an endless loop. You can either use the for, while, or until construct.

[me@linux ~]$ for ((;;)); do echo "infinite loop"; done
infinite loop
...

[me@linux ~]$ while :; do echo "infinite loop"; done
infinite loop
...

[me@linux ~]$ until false; echo "infinite loop"; done
infinite loop
...
How to find if a number is odd or even in bash?

You can use a bash for loop with a nested bash if statement using a bash arithmetic expansion with the module operator.

for x in {1..5}; do
  if (( x % 2 )); then
    echo "\$x=$x is an odd number"
  else
    echo "\$x=$x is an even number"
  fi
done

# output
$x=1 is an odd number
$x=2 is an even number
$x=3 is an odd number
$x=4 is an even number
$x=5 is an odd number
How to iterate over a bash array?

A Bash Array can be easily iterated over with a for loop construct and the array at @ notation. For a detailed example of a loop over a bash array, see How to iterate over a Bash Array? (loop).

[me@linux ~]$ myArray=(a b c)
[me@linux ~]$ for letter in "${myArray[@]}"; do echo $letter; done
a
b
c
How to loop over each line of a file?

In Bash, you must use a read-while loop or the mapfile builtin command to safely parse the line of a file. A read-while loop is a while loop using the read command with the -r option. You should never use a for loop with other commands, like cat or ls, as it would lead to errors when the filenames or file content contains whitespaces or any bash wildcards. You can find a detailed example in my post on How To Parse A CSV File In Bash?.

? The mapfile method is described more extensively in my post on bash arrays.

# WRONG
[me@linux ~]$ for line in $(cat myFile.txt); do echo "$line"; done
a
b
c
white
spaced line

# CORRECT
[me@linux ~]$ while read -r line; do echo "$line"; done < myFile.txt
a
b
c
white spaced line

# ALSO CORRECT
[me@linux ~]$ mapfile -t myArray < myFile.txt
[me@linux ~]$ for line in "${myArray[@]}"; do echo "$line"; done
How to iterate over a list of files?

When iterating over a list of files with a for loop, you shouldn’t use the ls command or other alternative command line as it may return incorrect results due to newline characters, whitespaces, or any other wildcards present in the filename. Instead, you should directly use the bash filename expansion using globbing and test that the file exists using a conditional expression to prevent the output of the globbing pattern as a string when no filename matches the pattern.

Alternatively, you can use the nullglob shell options, read more in my post on What Is The Best Way To Count Files In A Directory?.

# GOOD
[me@linux ~]$ for file in tmp/*; do echo $file; done
tmp/file1
tmp/file2
tmp/tmp2

# RETURN globbing pattern when no file found
[me@linux ~]$ for file in tmp/* tmp3/*; do echo $file; done
tmp/file1
tmp/file2
tmp/tmp2
tmp3/*

# CORRECT
[me@linux ~]$ for file in tmp/* tmp3/*; do { [[ -e $file ]] || continue; echo $file; } done
tmp/file1
tmp/file2
tmp/tmp2
How to use numbers with leading zeros in a bash loop?

To show leading zeroes in a bash loop, you can either add it to the bash brace expansion notation or if a variable is required by using the printf builtin with the optional -v parameter to re-assign the formatted value. The printf method is the only way to properly do zero-padding in a shell script.

[me@linux ~]$ for x in {01..05}; do echo "\$x equals to $x"; done
$x equals to 01
$x equals to 02
$x equals to 03
$x equals to 04
$x equals to 05

[me@linux ~]$ for ((x=1 ; x<=5 ; x++)); do { printf -v x %02d $x; echo "\$x equals to $x"; } done
$x equals to 01
$x equals to 02
$x equals to 03
$x equals to 04
$x equals to 05
How to iterate over a range of numbers defined by variables?

You can’t use variables inside the Bash Brace Expansion, instead you will need to use a for loop with a Bash Arithmetic Expression.

[me@linux ~]$ start=1
[me@linux ~]$ end=5
[me@linux ~]$ for ((i=start; i<=end; i++)); do echo $i; done
1
2
3
4
5

Ref From: shell-tips

Related articles