Loops
Loop, as the name implied, is execute a set of code repeatedly. Under one clear condition, BASH allows looping to happen. Here, we will look into several types of looping mechanism.
for
For looping mechanism is a single feed known listing loop mechanism. It requires a known list as a feed to its loop.
Pattern
There are various way to write the for loop. However, as a good practice, you write it:
for variable in (list of items); do
...
echo "Use $variable for each elements in the list."
done
Good Practice:
- keep to
do
the same line asfor
. It is the same as open bracket for most programming languages. - keep
done
as its own. It is the same as close bracket for most programming languages.
Basic Use
You can feed a list of items after in
keyword. For loop will read each of the element and pass it via the variable ($fruit
) for you to use.
#!/bin/bash
for fruit in banana papaya pineapple; do
echo "I love $fruit."
done
NOTE:
- Avoid applying quote to the list of items. Doing so will turn the whole thing into 1 single string element.
Feed An Array
Another common use is feeding the loop with an array. For loop will iterate through the list, get the element and save it inside your variable for your operations.
#!/bin/bash
list=(
"banana"
"papaya"
"pineapple"
)
for fruit in "${list[@]}"; do
echo "I love $fruit"
done
Search Directory
To search files in directory, you can use the wildcard and pattern searches. However, if there is no match (0 element), for loop will print the given pattern as a result. Therefore, you'll need to check the file existence before using the element. Here is an example for searching mp3 files in the current directory:
#!/bin/bash
for file in ./*.mp3; do
[ -e "$file" ] || continue # skip this if $file is ./*.mp3, which doesn't make sense.
...
done
C-Style Countable Loop
BASH specific feature - In C programming, we are used to make looping by a countable number within a specified range. This is doable in BASH looping as well. It complies to the following format:
for ((initial; condition to run loop; counter action)); do
...
done
Here's is an example for looping from 0 to 10:
for ((i=0; i<=10; i++)); do
echo "current number: %i"
done
NOTE:
- As a good practice:
- a space after the for keyword
- no space after the open parenthesis
- no space before the closing parenthesis
- one space after each semicolon statement separator (
;
)
- You don't need the dollar sign to denote the index variable.
Loop through Character for String
BASH specific feature - Unlike array looping, to loop through a string for each character can be very painful, yet achievable using for loop. The idea is to use the c-style counter loop against the length of the string. Here's the format, with all the usable elements:
total_char="${#string}"
for ((i=0; i<"$total_char"; i++)); do
char=${string:$i:1}
last_char="$((${#string} - 1))" # OR last_char="$((total_char - 1))"
...
done
Keep in mind that this type of looping is resource intensive and you should use it sparingly smartly. For example, if you don't need the $total_char
, don't set it.
While
While mechanism is a conditional iteration. It looks for a conditions to run the iterations or it meets its breaking condition (with a break
keyword).
Pattern
There are various way to write the while loop. However, as a good practice, you write it:
while [[ condition ]]; do
...
done
NOTE:
- keep to
do
the same line aswhile
. It is the same as open bracket for most programming languages. - keep
done
as its own. It is the same as close bracket for most programming languages. - the conditions can use both BASH new test conditions or POSIX test conditions, depending on portability requirement. Here, we'll use the BASH new test conditions.
Basic Incremental/Decremental Use
You can feed a condition to the while loop. The loop will repeat itself until the condition is no longer satisfied.
#!/bin/bash
i=0
while [[ "$i" -le 10 ]]; do
echo "I love repeating work."
...
((i++))
done
Infinity Loop and Break Triggers
You can also create an infinity loop with while mechanism. The loop will repeat itself until it hits the breaking condition. To break the loop, we use the keyword break
.
#!/bin/bash
i=0
while true; do
if [[ "$i" -gt 10 ]]; then
break
fi
echo "This is $i"
...
((i++))
done
Input Feeding Loop
While mechanism is also commonly used for reading contents by piping input. Here's is an example:
#!/bin/bash
while read ip name aliases; do
echo "This is: { $name }; IP: { $ip }; Aliases: { $alias }"
done < /etc/hosts
You need the read
command to populate the line into the list of variables you requested. This command splits the line component using the $IFS
variable.
Lastly, at the done
keyword, use the input pipe to provide a file path you wish to read.
Read File Line By Line
Using the input feeding loop approach, we can also read a file line-by-line. Here is an example:
#!/bin/bash
old_IFS="$IFS"
while IFS='' read -r line || [ -n "$line" ]; do
echo "$line"
...
done < "/path/to/file"
IFS="$old_IFS" && unset old_IFS
You need read -r
command to read the line as it is without special interpretation like backslash, configure your $IFS
to none for avoiding parsing complication and lastly, [ -n "$line" ]
to avoid last line being ignored if it it doesn't ends with newline (\n
).
Read File Line By Line with User Interaction
BASH specific feature - you can also read file line-by-line while prompting user interaction. This requires the use of stdin
redirect using exec
. Here is an example:
#!/bin/bash
exec 3<"./posix.sh"
old_IFS="$IFS"
while IFS='' read -r -u 3 line || [ -n "$line" ]; do
read -p "> $line (Press ENTER to continue)"
...
done
IFS="$old_IFS" && unset old_IFS
The difference is that instead of feeding the file directly into the while loop, you feed it into a redirect channel (in the example, it is 3). This frees up the stdin
for user prompting usage like "Press ENTER to continue".
Loop Through Characters for String
Looping through characters for string using while loop is possible as well. Basically it requires you to duplicate the string into a temporary variable since the loop consumes the string character by character. Here, we use the string manipulation to extract the first character:
- Get the first character (taking the string and subtract the remainder) -
$char
- Update index if available -
"$((index + 1))"
#!/bin/bash
string="a quick brown fox jumps over the lazy dog"
total=${#string}
index=0
while [ $index -lt $total ]; do
char="${string:${index}:1}"
last_char_index="$((total - 1))" # OR last_char_index="$((${#string} - 1))"
...
index="$((index + 1))"
done
unset index
This loop is lighter than the for loop variant. However, you still need to consider optimizing into suitable efficiency. Example: if you don't need $total
or $last_char_index
, don't use them.
Until
until mechanism is similar to while mechanism except the condition works in an opposite manner. It looks for a conditions to break the iterations or it meets its breaking condition (with a break
keyword). The pattern is:
Pattern
There are various way to write the while loop. However, as a good practice, you write it:
until [[ condition ]]; do
...
done
NOTE:
- keep to
do
the same line asuntil
. It is the same as open bracket for most programming languages. - keep
done
as its own. It is the same as close bracket for most programming languages. - the conditions can use both BASH new test conditions or POSIX test conditions, depending on portability requirement. Here, we'll use the BASH new test conditions.
An example:
#!/bin/bash
i=0
until [[ "$i" -ge 10 ]]; do
echo "the index is now: $i"
((i++))
done
Select
select mechanism is a closed loop for making user to select a fixed list of options. It is a kind of infinity loop and only breaks when meeting with the break
keyword.
Pattern
There are various way to write the while loop. However, as a good practice, you write it like:
select variable in item1 item2 ...; do
if [[ "$variable" == "exit keyword" ]]; then
break
fi
...
done
NOTE:
- keep to
do
the same line asselect
. It is the same as open bracket for most programming languages. - keep
done
as its own. It is the same as close bracket for most programming languages. - the break condition can be both BASH new test conditions or POSIX test conditions, depending on portability requirement. Here, we'll use the BASH new test conditions.
- you must plan your break triggers (be it timeout or user interaction) explicitly before further developing the loop functionalities.
Basic Select
Select is useful for gather user input within your fixed value compounds. This saves a lot of resources through reducing user input validation. Here is an example of basic number listing along with exit.
#!/bin/bash
select item in "one" "two" "three" "four" "five" "exit"; do
echo "You have chosen $item."
if [[ "$item" == "exit" ]]; then
break
fi
done
Running this code produces:
holloway:Desktop$ ./demo.sh
1) one
2) two
3) three
4) four
5) five
6) exit
#? 1
You have chosen one.
#? 2
You have chosen two.
#? 3
You have chosen three.
#? 4
You have chosen four.
#? 5
You have chosen five.
#? 6
You have chosen exit.
holloway:Desktop$
Notice that upon select 6, you get to break the loop and complete the program. Otherwise, it will print the positioned value.
List Select
Select mechanism allows the use of array as well (instead of a long string). Here's an example based on the basic version:
#!/bin/bash
list=(
"one"
"two"
"three"
"four"
"five"
)
list+=("exit")
select item in "${list[@]}"; do
echo "You have chosen $item."
if [[ "$item" == "exit" ]]; then
break
fi
done
This produces the same result as the basic select.
Changing Prompt Text with $PS3
Notice that the prompt for user input is #?
? That is the default PS3 value. You can change your prompt messages by changing the text. Here is an example:
#!/bin/bash
list=(
"one"
"two"
"three"
"four"
"five"
)
list+=("exit")
PS3="Please select number (1-${#list[@]}): "
select item in "${list[@]}"; do
echo "You have chosen $item."
if [[ "$item" == "exit" ]]; then
break
fi
done
This will prompt a different message instead:
holloway:Desktop$ ./demo.sh
1) one
2) two
3) three
4) four
5) five
6) exit
Please select number (1-6): 1
You have chosen one.
Please select number (1-6): 2
You have chosen two.
Please select number (1-6): 3
You have chosen three.
Please select number (1-6): 4
You have chosen four.
Please select number (1-6): 5
You have chosen five.
Please select number (1-6): 6
You have chosen exit.
holloway:Desktop$
Nested Loop
It is possible to have a loop inside of another loop. However, as a best practice to avoid cyclomatic complexity, you should always makes good use of your 3-tabs warning.
If you need more than 3-tabs, you are likely going to produce a highly complex codes. Try break it down using function or simplify the process.
That's all about looping in BASH. Feel free to move up to the next section.