How To Use Bash If Statements (With Code Examples)

Andrei Dumitrescu
Andrei Dumitrescu
hero image

Looking to upgrade your Bash knowledge, or maybe just struggling to grasp Bash If Statements?

Well, look no further! In this article, we'll dive deep into Bash if statements, and discuss:

  • conditional statements
  • if else
  • elif
  • logical operators
  • nested blocks
  • arithmetic comparisons
  • string comparisons
  • case statements

And a whole lot more, so that by the end of this guide, you'll be able to add more logic to your scripts, while also creating really useful stuff for your daily job.

Bash Scripting: An Overview

Alright so a quick few things up top.

I'm assuming if you landed on this tutorial, you know what Bash Shell Scripting is, but just in case, let's quickly make sure we're on the same page.

  • The Bourne shell was the default operating system for Unix 7
  • Bash is the updated version, otherwise known as 'Bourne-Again SHell' (BASH)
  • Bash implements similar grammar as the Bourne shell, but there are some difference in how it works - hence why people sometimes get confused, especially when it comes to conditional logic

So let's break it down.

Understanding conditions in Bash if statements

if, elif, and else statements are all used for decision making which is one of the most fundamental concepts in computer programming.

This is also known as 'program flow control', and it allows us to have logic in our scripts, so that they execute a section of code only when a particular criteria is met.

if, elif and else statements in bash

Simple so far, but lets dive deeper...

Reserved words in Bash

Reserved words are words that have a specific meaning to the Bash shell, and will either begin or end the shells compound commands.

For example

  • Here
  • if
  • then
  • elif
  • else, and
  • fi

Are all Bash reserved words, and usually used alongside some condition as a sort of comparison operation.

For example

  • Testing if the value of a variable is greater than a specific number, or
  • if an argument of the script is either a file or a directory, or
  • if you have the read permission on a specific file

If we look at the image again from earlier:

if, elif and else statements in bash

Basically, the above code says something like this:

  • if the first condition is True then execute only this piece of code below,
  • But if the first condition is evaluated to False, then test the second condition that comes after elif instead
  • However, if the second condition is True, then execute only that block of code, but if it’s False, execute the block of code that comes after the else clause

I promise it will get easier to understand as we go through.

Before going any further though, there are a few other important things to know and remember.

Thing #1. Order of execution

The conditions are evaluated sequentially.

This means that once a test condition returns True, the remaining conditions will not be evaluated anymore and program control moves to the end of the if construct, so after the fi line.

Thing #2. fi

fi is just if written backwards, and ends the if construct which is in fact a shell compound command.

Thing #3. Compound commands

Remember that each Bash compound command starts and ends with some reserved words. if and fi in this case.

Thing #4. True means zero

In Bash True means a return status of zero, while False means a return status different from zero.

Thing #5. We can only have one if statement

Note that we can have only one if statement and it’s not mandatory to always have elif or else statements.

Thing #6. Whitespace is mandatory

However, it is mandatory to have a whitespace before and after the square brackets that enclose the testing condition.

Thing #7. fi delimiters

Also, the fi reserved word at the end delimiters the entire if, elif, and else programming structure. It’s similar to a closing curly brace "}" in other programming languages like C or Java.

Phew!

Again, this will become more clear as we work though, so let’s go to coding and see some examples, so you can see this logic working, and how it changes the code when used.

Below, we're creating a script that takes a regular file as an argument and displays its contents, like so:

  1 #!/bin/bash
  2 cat $1

As you can see, $1 is the first argument of the script, and I’m also giving it a regular file as an argument:

dda@victus:~/lab/bash$ chmod 700 display.sh 
dda@victus:~/lab/bash$ ./display.sh urls.txt 
www.google.com
www.twitter.com
www.yahoo.com
www.youtube.com
www.wikipedia.com
www.netflix.com
www.ubuntu.com

So that worked fine.

But what will happen if instead of giving the script an existing file as an argument, I give it a directory or a non-existing file?

Well, in that case the cat command will return an error.

dda@victus:~/lab/bash$ ls -ld project
drwxrwxr-x 2 dda dda 4096 Oct 18 14:32 project
dda@victus:~/lab/bash$ ./display.sh project
cat: project: Is a directory

OK, so that means some sort of testing is necessary, and here's where we can start using some conditional logic.

I want to display the contents of the first argument of the script only if that argument is of type regular file, so I’m adding an if statement with a test condition:

  1 #!/bin/bash
  2 if [ -f "$1" ]
  3 then
  4         cat $1
  5 fi

-f is a test condition that returns true if what follows is a regular file.

This means that the piece of code between then and fi will be evaluated and run only if $1, so the first argument of the script is of type regular file.

Sidenote: If you want to see all of the testing conditions that can be used, then run: man test

A final remark about this code is that I’ve enclosed the $1 variable in the test condition in double quotes, but do you know why I did it this way?

Its because the result of an expansion that does not occur within double quotes is subject to word splitting.

Therefore, I used double quotes because I don't want the result of the $1 expansion to be subject to word splitting. This way it allows whitespaces in the filename, which is a small detail but it can make a difference in some cases.

Later in this article, I'll also show you another way to prevent word splitting, but before then, let’s run the script again, giving it a directory as an argument or a file that doesn’t exist.

dda@victus:~/lab/bash$ ls -ld project
drwxrwxr-x 2 dda dda 4096 Oct 18 14:32 project
dda@victus:~/lab/bash$ ./display.sh project
dda@victus:~/lab/bash$ ./display.sh non_existing

As you can see, there are no errors because the cat command was not executed, since the test condition returned false. Huzzah!

So, let’s go ahead and add a second test condition.

In this one, if the passed argument is a directory, not a file, then I’ll run ls -l on it.

#!/bin/bash
if [ -f "$1" ]
then
	echo "The argument is a file, displaying its contents ..."
	sleep 1
	cat $1
elif [ -d "$1" ]
then 
	echo "The argument is a directory, running ls -l ..."
	sleep 1
	ls -l $1
else
	echo "The argument ($1) is neither a file nor a directory."
fi

Also, I'm running the new script:

dda@victus:~/lab/bash$ ls -ld project
drwxrwxr-x 2 dda dda 4096 Oct 18 14:32 project
dda@victus:~/lab/bash$ ./display.sh project
The argument is a directory, running ls -l ...
total 0
-rw-rw-r-- 1 dda dda 0 Oct 18 14:33 a.cpp
dda@victus:~/lab/bash$ ./display.sh urls.txt 
The argument is a file, displaying its contents ...
www.google.com
www.twitter.com
www.yahoo.com
www.youtube.com
www.wikipedia.com
www.netflix.com
www.ubuntu.com
dda@victus:~/lab/bash$ ./display.sh non_existing
The argument (non_existing) is neither a file nor a directory.

Finally, I want to tell you something about single square brackets, [ ] vs. double square brackets [[ ]].

The examples shown here use single square brackets for test conditions. This is the old, classical, and portable type of testing.

There is however a new, updated version of testing that uses double square brackets as they are considered safer.

How are they safer?

Basically, double square brackets prevent word splitting of string variables that contain spaces, so you don’t need to double quote string variables, even though that’s good practice.

[[ also have some other nice features, like regular expression matching, but for the moment though don’t focus too much on this. You can use single square brackets if you don’t know what to use. I just wanted you to be aware of this option.

Arithmetic comparisons

Let’s go ahead and see how to work with numbers and if test conditions.

To see the available tests, we can run man test and see all the comparison operators that can be used for integer comparison.

(Remember that in Bash there are only integers.)

arithmetic comparisons in bash

The arithmetic operators used for test conditions are:

  • -eq is used to compare whether two integers are equal or not. It’s the equivalent of == in other programming languages
  • -ge will test whether the integer on the left side is greater than or equal to the integer on the right side. (It’s the equivalent of >= in other programming languages)
  • -gt will test whether the integer on the left side is greater than the integer on the right side. (It’s the equivalent of > in other programming languages)
  • -le will test whether the integer on the left side is less than or equal to the integer on the right side. (It’s the equivalent of <= in other programming languages
  • -lt will test whether the integer on the left side is less than the integer on the right side. It’s the equivalent of < in other programming languages
  • -ne will return true if the operands are not equal

Let's see an example:

#!/bin/bash
read -p "Enter your age: " age

if [[ $age -lt 18 ]]
then
	echo "You are minor!"
elif [[ $age -eq 18 ]]
then
	echo "Congratulations, you've just become major!"
else
	echo "You are major!"
fi

The script will prompt the user for their age and will display a message according to the value entered.

dda@victus:~/lab/bash$ chmod 700 age.sh 
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 10
You are a minor!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 18
Congratulations, you've just become a major!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 70
You are a major!

Multiple condition tests and nested if/then statements

OK, so lets talk about multiple conditions and nested if statements.

If we look at the script that we have so far, it prompts the user for a number, and tests whether the number is less than 18, equal to 18, or is greater than 18.

The script works just fine, but there's a problem with the logic of the script.

If the user enters a very large number for the age, like 200, the script will print out: "you are a major!", and we can all agree that there’s no one that old still alive. (Unless vampires etc).

So, we can solve the problem by adding a nested if statement or even better, using the logical AND operator. All I have to do is add another condition so that the age is always positive and less than 100.

The logical OR and AND operators allow you to use multiple conditions in the same if statement.

logical operators in Bash

With this in mind, I'm changing the script:

#!/bin/bash
read -p "Enter your age: " age

if [[ $age -lt 18 && $age -ge 0 ]]
then
        echo "You are a minor!"
elif [[ $age -eq 18 ]]
then
        echo "Congratulations, you've just become a major!"
elif [[ $age -gt 18 && $age -le 100 ]]
then
        echo "You are a major!"
else
        echo "Invalid age!"
fi

Alright, so let's run the script:

dda@victus:~/lab/bash$ ./age.sh 
Enter your age: -7
Invalid age!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 5
You are a minor!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 18
Congratulations, you've just become a major!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 71
You are a major!
dda@victus:~/lab/bash$ ./age.sh 
Enter your age: 200
Invalid age!

Note that if you use single square brackets you can use -a instead of && and -o, instead of ||

Let’s take a look at a new example.

Let’s return to the script created at the beginning of this article, which takes an argument and checks if the argument is of type file or directory.

If it’s a file it displays its contents using cat, and if it’s a directory it lists its contents using ls -l.

We’ve already tested it and it worked just fine. However, the problem is that the argument is mandatory. This means that if the script is run without an argument it will display the wrong message and say that the argument is neither a file nor a directory.

dda@victus:~/lab/bash$ ./display.sh 
The argument () is neither a file nor a directory.

It would be better to check if there’s an argument, and only in that case to start testing if it’s a file or a directory and execute the commands.

Now, keep in mind that the number of arguments of a script is stored in a special predefined variable named $#, so I’m adding a testing condition to see if there’s exactly one argument.

I'm also changing single brackets [ ] to double brackets [[ ]].

#!/bin/bash
if [[ $# -eq 1 ]]  
then 
	if [ -f "$1" ]
	then
		echo "The argument is a file, displaying its contents ..."
		sleep 1
		cat $1
	elif [ -d "$1" ]
	then 
		echo "The argument is a directory, running ls -l ..."
		sleep 1
		ls -l $1
	else
		echo "The argument ($1) is neither a file nor a directory."
	fi
else
	echo "The script should be run with an argument"
fi

And I’m testing it by running the script with a different number of arguments.

dda@victus:~/lab/bash$ ./display.sh 
The script should be run with an argument
dda@victus:~/lab/bash$ ./display.sh project
The argument is a directory, running ls -l ...
total 0
-rw-rw-r-- 1 dda dda 0 Oct 18 14:33 a.cpp
dda@victus:~/lab/bash$ ./display.sh users.txt 
The argument is a file, displaying its contents ...
u1:gr1
u2:gr2
u3:gr3
dda@victus:~/lab/bash$ ./display.sh non_existing
The argument (non_existing) is neither a file nor a directory.

It’s much better!

By the way, this is called a nested if, because there’s an inner if statement inside an outer one.

String comparisons

Let's move on!

When automating different common administrative tasks using shell scripts, you will often need to compare the output of a command, which is a string, to another string value to see whether they are equal or not and based on the result, to act accordingly.

string comparisons in bash

Let’s see how to compare strings in Bash.

Two strings are equal when they have the same length and contain the same sequence of characters in the same order.

The comparison operator for strings is the single equal sign if you use the if statement with single square brackets, or the double equal sign if you use the if statement with double square brackets.

There’s also the inequality operator that returns true if the strings are not equal. As in any other programming language, this operator consists of an exclamation mark and an equal sign (!=).

Let’s move on and see how to check if a string contains a substring.

There are multiple ways to do that, but one approach is to surround the substring with asterisk symbols, which means matching all characters.

I’m creating a new script for this example:

#!/bin/bash

str1="Nowadays, Linux powers the servers of the Internet."

if [[$str1== *"Linux"* ]]
then
        echo "The substring Linux is there."
else
        echo "The substring Linux IS NOT there"
fi

And if we check that...

dda@victus:~/lab/bash$ ./substring.sh 
The substring Linux is there.

Sometimes you want to check if a string resulting from another operation like a command substitution is empty or not.

You can do this by using an if statement with -n or -z.

  • -n returns True if the string length is non-zero
  • -z returns True if the string length is zero
  1 #!/bin/bash
  2 my_str="abc"
  3 if [[ -z "$my_str" ]]
  4 then
  5         echo "String is zero length."
  6 else
  7         echo "String IS NOT zero length."
  8 fi
  9 
 10 my_str=""
 11 if [[ -n "$my_str" ]]
 12 then
 13         echo "String IS NOT zero-length"
 14 else
 15         echo "String is zero length"
 16 fi

Let's run it!

dda@victus:~/lab/bash$ ./empty_string.sh 
String IS NOT zero length.
String is zero length

And that's it! Now its your turn

Phew, that was a lot to cover, but hopefully by now you understand bash if statements a little better, and can get to work adding them into your own projects.

If you still have questions about Bash and want to learn more, check out my complete Bash Scripting course.

I guarantee that it's is the most comprehensive and up-to-date online resource to learn Bash Scripting. Plus, we'll give you the exact steps you need to take to get hired as a SysAdmin, DevOps Engineer, or Network Engineer no matter what level of experience you have.

Alternatively, you can also check out the first lessons of my Bash course for free here.

More from Zero To Mastery

Top 5 In-Demand Tech Jobs For 2024 (+ How To Land A Job In Each!) preview
Top 5 In-Demand Tech Jobs For 2024 (+ How To Land A Job In Each!)

Want to get hired in a tech job in 2024? Pick one of these 5 if you want: 1) High salary 2) Jobs available now 3) Can learn the skills as a complete beginner.

How to Get More Interviews, More Job Offers, and Even a Raise preview
How to Get More Interviews, More Job Offers, and Even a Raise

This is Part 3 of a 3 part series on how I taught myself programming, and ended up with multiple job offers in less than 6 months!

How To Find Rootkits On Your Linux Device preview
How To Find Rootkits On Your Linux Device

Thanks to a growing focus on Enterprise by hackers, Linux machines are not as safe as they used to be. Check your device now with this step-by-step guide.