Using Case Statements in Bash

When writing Bash scripts, it is common to check a string against a variable, and then do something based on what was specified.

Consider the following script. It checks the first argument that a user specifies, and does different things depending on what was given:

#!/usr/bin/env bash
if [[ "${1}" == "" ]]; then
	echo "No shape was specified."

elif [[ "${1}" == "square" ]]; then
	echo "You specified 'square'."

elif [[ "${1}" == "circle" ]]; then
	echo "You specified 'circle'."

elif [[ "${1}" == "triangle" ]]; then
	"You specified 'triangle'." 
    
else
	echo "Unknown shape '${1}'."
    exit 1

fi

This demonstrates, albeit with no functionality, a simple task that can repeat a known shape back to a user, or if the shape is unknown, show an error message.

Everything's fine and dandy in this, and it all works as expected. But look at how many lines that took. See how you have to handle every single option that a user passes through an elif statement?

Now imaging this program has even more options, say 20. With the current approach, you are going to be writing an if statement with 20 elif sections inside it.

Yeah, you'll probably be able to read it for the most part. But it's clunky, takes up too much space, and most importantly, there is a better alternative.

Introducing the case statement in Bash

This is where case statements come in. These can reduce the number of lines of code you need for checking things by a long shot compared to if statements.

The same if statement you used to check for the provided options could be rewritten like so inside of a case one:

#!/usr/bin/env bash
case "${1}" in
	"")         echo "No option was specified."; exit 1 ;;
    square)     echo "You specified 'square'." ;;
    circle)     echo "You specified 'circle'." ;;
    triangle)   echo "You specified 'triangle'." ;;
    *)          echo "Unknown shape '${1}'."; exit 1 ;;
esac

Look how much simpler and organized that looks. That is 12 lines of code in the if statement, and only 7 in the case one. And the more options you allow, the more drastic that difference becomes.

And that's where case statements shine. While both are completely legible, especially at a small amount, case statements simply make everything cleaner and more maintainable in the long run.

How do you create a case statement in Bash?

Case statements are pretty simple to understand with just a little bit of practice. With that, here's the basics to what's going on:

The first line you saw was case ${1} in. The ${1} gives the string that will be checked against, here being the first argument passed to the script.

For example, consider this case statement:

case linux in
    windows)    echo "windows" ;;
    macos)      echo "macos" ;;
    linux)      echo "linux" ;;
esac

In this example, it will always run echo "linux". The case *string* in part simply specifies what string you'll be checking against, and then the options below it will run the commands whenever a match is found.

Next, you start specifying your options. Remember, you had "", square, circle, triangle, and * as options in your first script.

Case statements are checked up to down, and only run on the first match. Consider the following example:

case linux in
	linux)      echo "linux" ;;
    windows)    echo "windows" ;;
    linux)      echo "linux 2.0" ;;
esac

Here, the only command that would run would be echo "linux". The second match would then be skipped, as there was a match that was already found.

Each line in a case statement must also end in ;;. This signifies the end of the commands list for an option, and that no further commands should be processed.

The arguments list in a case statement (the linux) and windows) parts also support wildcards. Thus, entering something like linu*) would match both linux and linus.

Lastly, you end the case statement with esac. This lets it know that you have listed everything you would like it to process, like done would do in a for or while loop.

To remember the keyword for finishing a case statment, just remember that esac is case backwards.

Case statements in practice - argument checks

A frequent use of case statements in my own programming is checking against an arguments list.

Say you allow the following options to be provided to your program:

  • --vertical
  • --add-txt
  • --replace
  • --verify

To check this in a case statement, you could use something like this:

while [[ "${1}" != "" ]]; do
	case "${1}" in
    	--vertical)    verticle="true" ;;
        --add-txt)     add_txt="true" ;;
        --replace)     replace="true" ;;
        --verify)      verify="true" ;;
    esac
    
    shift 1
done

This runs a while loop while ${1} (the first argument given to a script) is not empty. It then checks against an argument list, and assigns variables when the provided option is set.

After checking against the case statement, it then runs shift 1, causing the value of ${2} (the second argument provided to a script) to move to the value of ${1}, and repeats this against the new ${1} (which was previously ${2}) until ${1} isn't set to anything.

And there you go! That's all the basics to using case statements.

Wrapping Up

Case statements can help you to write code both more quickly, and more efficiently. They also help to make things more legible and maintainable in the long run.

If you are new to Bash, you may learn the basics of Bash from scratch in our Bash Beginner Series.

Bash Tutorials for Beginners: Start Learning Bash Scripting
Here’s a collection of bash tutorials that will teach you bash shell scripting from the beginning. You’ll learn all the basics of bash scripting.

Got any questions, or something not working quite right? Feel free to leave a comment below, and I'll try my best to respond back!