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 looping mechanism is a single feed known listing loop mechanism. It requires a known list as a feed to its loop.
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."doneGood Practice:
do the same line as for. It is the same as open bracket for most programming languages.done as its own. It is the same as close bracket for most programming languages.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/bashfor fruit in banana papaya pineapple; do echo "I love $fruit."doneNOTE:
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/bashlist=( "banana" "papaya" "pineapple")for fruit in "${list[@]}"; do echo "I love $fruit"doneTo 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/bashfor file in ./*.mp3; do [ -e "$file" ] || continue # skip this if $file is ./*.mp3, which doesn't make sense. ...doneBASH 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 ...doneHere's is an example for looping from 0 to 10:
for ((i=0; i<=10; i++)); do echo "current number: %i"doneNOTE:
;)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))" ...doneKeep 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 mechanism is a conditional iteration. It looks for a conditions to run the iterations or it meets its breaking condition (with a break keyword).
There are various way to write the while loop. However, as a good practice, you write it:
while [[ condition ]]; do ...doneNOTE:
do the same line as while. It is the same as open bracket for most programming languages.done as its own. It is the same as close bracket for most programming languages.You can feed a condition to the while loop. The loop will repeat itself until the condition is no longer satisfied.
#!/bin/bashi=0while [[ "$i" -le 10 ]]; do echo "I love repeating work." ... ((i++))doneYou 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/bashi=0while true; do if [[ "$i" -gt 10 ]]; then break fi echo "This is $i" ... ((i++))doneWhile mechanism is also commonly used for reading contents by piping input. Here's is an example:
#!/bin/bashwhile read ip name aliases; do echo "This is: { $name }; IP: { $ip }; Aliases: { $alias }"done < /etc/hostsYou 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.
Using the input feeding loop approach, we can also read a file line-by-line. Here is an example:
#!/bin/bashold_IFS="$IFS"while IFS='' read -r line || [ -n "$line" ]; do echo "$line" ...done < "/path/to/file"IFS="$old_IFS" && unset old_IFSYou 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).
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/bashexec 3<"./posix.sh"old_IFS="$IFS"while IFS='' read -r -u 3 line || [ -n "$line" ]; do read -p "> $line (Press ENTER to continue)" ...doneIFS="$old_IFS" && unset old_IFSThe 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".
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:
$char"$((index + 1))"#!/bin/bashstring="a quick brown fox jumps over the lazy dog"total=${#string}index=0while [ $index -lt $total ]; do char="${string:${index}:1}" last_char_index="$((total - 1))" # OR last_char_index="$((${#string} - 1))" ... index="$((index + 1))"doneunset indexThis 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 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:
There are various way to write the while loop. However, as a good practice, you write it:
until [[ condition ]]; do ...doneNOTE:
do the same line as until. It is the same as open bracket for most programming languages.done as its own. It is the same as close bracket for most programming languages.An example:
#!/bin/bashi=0until [[ "$i" -ge 10 ]]; do echo "the index is now: $i" ((i++))doneselect 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.
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 ...doneNOTE:
do the same line as select. It is the same as open bracket for most programming languages.done as its own. It is the same as close bracket for most programming languages.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/bashselect item in "one" "two" "three" "four" "five" "exit"; do echo "You have chosen $item." if [[ "$item" == "exit" ]]; then break fidoneRunning this code produces:
holloway:Desktop$ ./demo.sh 1) one2) two3) three4) four5) five6) exit#? 1You have chosen one.#? 2You have chosen two.#? 3You have chosen three.#? 4You have chosen four.#? 5You have chosen five.#? 6You 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.
Select mechanism allows the use of array as well (instead of a long string). Here's an example based on the basic version:
#!/bin/bashlist=( "one" "two" "three" "four" "five")list+=("exit")select item in "${list[@]}"; do echo "You have chosen $item." if [[ "$item" == "exit" ]]; then break fidoneThis produces the same result as the basic select.
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/bashlist=( "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 fidoneThis will prompt a different message instead:
holloway:Desktop$ ./demo.sh 1) one2) two3) three4) four5) five6) exitPlease select number (1-6): 1You have chosen one.Please select number (1-6): 2You have chosen two.Please select number (1-6): 3You have chosen three.Please select number (1-6): 4You have chosen four.Please select number (1-6): 5You have chosen five.Please select number (1-6): 6You have chosen exit.holloway:Desktop$ 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.