BASH Basics - Part 6: Loops and Functions

The introduction into scripting ends with the final aspects of scripting with loops and functions. To see more about BASH, the command line, interesting BASH scripts, and command line functions, you can look at the BASH scripts and analyze them with your newfound knowledge from the last parts. We covered loops and functions briefly at the beginning, but since they are so important, this article contains more detail about them and how to use them.

Loops

BASH has three basic loop structures: the while-loop, the until-loop and the for-loop which we had seen earlier. So, where do we use which loop? while-loops are used as long as an expression evaluates to true. Let’s first look at a script which opens four terminals for us to avoid repetitive work:

4terminals.sh

#!/bin/bash
# This script opens 4 terminal windows
i="0"

while [ $i -lt 4 ] #test condition and while statement
do
#open terminal and background it until 4 windows are open
mate-terminal &
i=$[$i+1] #increment counter
done
If you set your variable to true, the loop runs indefinitely. You can get out of it with a ctrl-c or a break statement, which is covered below. To increment the counter, ((i++)), is also a valid way to do. Avoid “off-by-one” or “fencepost”-errors! Check when to use lt or le, larger than or larger or equal, by using a simple example first. until-loops are run until the test becomes true. By changing the statement in the above script to until and changing the test, we can achieve the same result:

4terminals2.sh

#!/bin/bash
# This script opens 4 terminal windows
i="0"

#test condition and until statement
until [ $i -ge 4 ]
do
#open terminal and background it until 4 windows are open
mate-terminal &
((i++)) #increment counter
done
By adopting the statement and the test, we achieve identical results. Why use a different statement, then? This is about clean and elegant code. You choose whatever is easiest to read, code, and understand in a particular situation. “Don’t touch the paint until it’s dry.” is easier to understand than “Don’t touch the paint while it’s not dry.”, or even “Don’t touch the paint while it is wet.” The for-loop was already covered; the for ... do ... done structure should be clear by now. With for i in {a..b}, we can also define ranges of values. A script with a range would look like this:

4terminalswithrange.sh

#!/bin/bash
# simple range in for loops
for i={1..4}

do
mate-terminal &
done

echo "Preparations completed!"
Be careful not to include spaces in the curly brackets, otherwise this will be seen as a list of items! If the first number is bigger than the second, the count is down instead of up; also, an added number after two more points like {a..b..c} will use an increment with the size of c. for-loops are incredibly useful when we want to process sets of files, as we have already seen in the earlier examples.

However, there are other ways to control loops and scripts: the break, continue and select commands. Imagine that you want to write a script to backup a set of files by copying them to another place, but only when the disk is less than 95% full:

backupfiles.sh

#!/bin/bash
# make a backup of files in dir
# usage: backupfiles.sh dir
for i in $1/*
do
level=$( df $1 | tail -1 | awk '{ print $5 }' | sed 's/%//' )
if [ $level -gt 95 ]
then
echo Low disk space 1>&2
break
fi
cp $i $1/backup/
done
With continue, you can stop the execution of code inside a loop and jump to the next iteration. If we want to extend the backup script, maybe we introduce a code block to alert us of files with insufficient read rights, which therefore cannot be copied:
for i in $1/*
do
if [ ! -r $i ]
then
echo $i not readable 1>&2
continue
fi
cp $i $1/backup/
done
The select command makes it possible to have a simple menu for data entry for given options: “select var in ; do ; done” is the syntax. There is no error checking; invalid input leaves var empty. The loop ends with a break statement, or an EOF signal, and the prompt can be changed by changing the system variable PS3. The following code demonstrates a practical application:

odroidid.sh

#!/bin/bash
# Odroid model selector

model='HC1 HC2 XU4 C1+ C2 Quit'

PS3='Select Odroid type: '

select name in $model
do
if [ $model == 'Quit' ]
then
break
fi
echo Your model is Odroid $model
done

echo End.

Functions

Functions are ways to reuse code, either in scripts or in your .bashrc file, where we already encountered them. Functions are final scripting item that will be introduced. With functions, you need to define them before calling them in BASH. A function definition is:

function function_name {

}

or alternatively

function_name() {

}
As usual, arguments passed to the function are accessed with $1, $2, and so on. BASH functions give a return status, by using return n in the function, where n is any number, and retrieving it with $? from the calling script. Conventionally, return status 0 indicates a run with no problems.

If a function does NOT return a result, you can work around this by using command substitution with $( function_name ) and having the function print out only the result. Then, you can assign a variable var=$( function_name ) with the value the function would normally print out.

Miscellaneous

Let’s have a look at some interesting BASH scripts which use the loop types and functions. An interesting, up-to-date collection is Bash Snippets by Alexander Epstein. You can install them with the following commands, or directly with git clone, like mentioned in the instructions on the Github webpage:

$ sudo add-apt-repository ppa:navanchauhan/bash-snippets
$ sudo apt update
$ sudo apt install bash-snippets
I want to look at two of these snippets more closely: geo and qrify. Please look at the scripts in your favorite editor. They are examples of good scripting and They exemplify the usage of what we have learned so far. You can find them with the usual commands, find / -iname '*qrify*' 2>/dev/null finds qrify.sh anywhere on your system regardless of your method of installation. First, a look at geo, as shown in Figure 1.

Figure 1 - geo

The help page of geo shows what you can do with it - a useful script to get the WAN and LAN IP of your ODROID, information about your network as well as geolocation information. A bit more flashy and less mundane is qrify:

With a syntax like in the example, qrify "WIFI:T:WPA;S:mynetwork;P:mypass;;", you can substitute mynetwork and mypass for your WLAN name and password and have the QR read by any modern smartphone. When you save it as a .png via qrify options, you can print it out or have it display on the screen for your guests. With a small SBC equipped with an e-paper display HAT, this might be an interesting new project to hand out WLAN codes at a reception desk. For the next part, there will be more helpful commands to get the most out of your ODROID. Stay tuned!

References https://github.com/alexanderepstein/Bash-Snippets

Be the first to comment

Leave a Reply