Expanding on the hello world script we have been looking at in previous posts, we will add some intelligence to our greeting using conditional logic. We'll also take a look at a few different methods for executing common arithmetic operations.

When I originally started writing this post I intended on including a section on looping, due to the length of this post I decided to leave it off and include it in the next installment of this series.

Good Morning World

Last we looked at our hello world script we had managed to add some color and use a variable to store part of our greeting. While that wasn't very exciting it lays the foundation to do some more interesting things.

In this example we are going to say Good Morning World, Good Afternoon World, and Good Evening World depending on the time of day. Let's take a look at the script and then walk through what is happening.

#!/bin/bash
set -e
set -u

hour_of_day=$(date +%H)

if [[ $hour_of_day -lt 12 && $hour_of_day -ge 5 ]]; then
    time_of_day="Morning"
elif [ $hour_of_day -lt 18 ]; then
    time_of_day="Afternoon"
else
    time_of_day="Evening"
fi

printf "Good ${time_of_day} World\n"

Command Substitution

The first interesting thing about the new additions to our script is the $(date +%H). This is referred to as command substitution. Command substitution executes whatever command is specified within the $( ) and treats the result as a string. In this example it is returning the hour of the day and storing that in the $hour_of_day variable.

Conditional Logic

Next we move on to the conditional logic. In this case we are using the if command. The basic structure of an if command is as follows:

if [ <some expression> ]; then
    <commands>
fi

One thing to remember is that if you want to string together several conditions in a single expression, you have to use the && (and) or the || (or) operators. In our example we are using &&. Whenever you do this you also need to enclose the expression in a double set of square brackets:

if [[ <some expression> && <some other expression> ]]; then
    <commands>
fi

Expressions

An expression is a statement that evaluates to either true or false. In our case we are just checking if the $hour_of_day variable is less than 12 and greater than or equal to 5. When comparing numeric values you must use the same operators you are familiar with from PowerShell: -lt -gt -le -ge -ne. When comparing string values you have to use operators you might be more used to from other languages: == != and both values must be enclosed in double quotes [ "string1" == "string2" ].

Else If

The if command also has support for else and elif (else if), to allow you to set up more complex logical checks. The structure doesn't change much but there a few things to call out:

if [ <some expression> ]; then
    <commands>
elif [ <some expression> ]; then
    <commands>
else
    <commands>
fi

Looking at the example above, if and elif behave roughly the same. They expect an expression and a command terminator ;, and must be followed by a then statement. Where they differ is that the elif does not have it's own closing statement, it relies on the closing fi instead. The else statement is even simpler, not requiring an expression or a then statement.

In our example we are using the elif to check if the $hour_of_day variable is less than 18 but greater than 12, and finally the else to catch cases where the $hour_of_day is greater than 18.

There are a huge number of expressions you can use in your if command (including regex matching). A deep dive into if is outside the scope of this post, but there is a great guide on all the options here: Introduction to If.

A Case for CASE

While the if/elif/else construct is nice an familiar, sometimes a simple case statement will do the trick. Here is an example of the if logic from our first example using a case statement:

#!/bin/bash
set -e
set -u

hour_of_day=$(date +%H)

case $hour_of_day in
    [5-11]*)
        time_of_day="Morning"
        ;;
    [12-17]*)
        time_of_day="Afternoon"
        ;;
    *)
        time_of_day="Evening"
        ;;
esac

printf "Good ${time_of_day} World\n"

This code accomplishes the same thing as the if logic, it just looks a bit different. I recommed reading up on case as it can be useful, but as long as you have a good grasp of if/elif/else you should have everything you need in most cases. Here are a few examples of using case in different ways: 5 Bash Case Statement Examples

Arithmetic

Doing arithmetic in bash is not nearly as straight forward as it is in PowerShell, but it is still possible.

Integers

Integer arithmetic can be accomplished with this simple syntax:

(( my_var = 1 + 2 ))
printf "${my_var}\n"

All we need to do for integer arithmetic is enclose the operation (and variable assignment) in double parenthesis. This works for all types of operations, but always be mindful that any decimals will be discarded. For example:

(( my_var = 5 / 2 ))
printf "${my_var}\n"

This command will print a 2 to the screen. The remaining 0.5 has been discarded. This method also respects order of operations, and grouping operations in additional parenthesis:

(( my_var = 5 * (2 + 1) ))
printf "${my_var}\n"

Floating Point Numbers

Since there is no native support for floating point numbers in bash, we have to use an external utility called bc which is available on most systems (with the exception of some embedded systems):

printf "$(echo "scale=2;5 / 2" | bc)\n"

This is not the prettiest I will admit. Using bc we have to "feed" it the equation and scale (precision) information by way of echo (or any other standard input method). If you do not supply a scale in your command, bc will behave differently than expected for some operations. For example, if you try:

printf "$(echo "5 / 2" | bc)\n"

You will see it returns 2. To avoid this, you can either set the scale with each command or create a configuration file.

bc Configuration

To create a configuration file, create a new file in your home directory called .bc and put the following in it:

scale=2

The scale can be whatever you like. Then you need to set an environment variable to tell bc where to find the config file. The best way to do this is to add the following to your .bash_profile file:

BC_ENV_ARGS='/home/<your user name here>/.bc'
export BC_ENV_ARGS

To immediately apply this change, just type the following at a command prompt:

mark@atomo:~ >$ source ~/.bash_profile

bc can do almost any mathmatical operation you can think of. To read more about bc take a look at the manual over at gnu.org: bc Command Manual

Final Thoughts

Conditional logic and arithmetic are some of the most important building blocks when constructing more complex scripts. While native arithmetic operations are a bit limited in Bash, bc more than makes up for it. In the next post we are going to spend some time looking at looping methods using for, while, and xargs.