Bash Advanced Scripting: Case Statements and Functions - A Complete Beginner's Guide (Part 1)

Master Bash case statements for multi-condition logic and learn to create reusable functions with parameters. Build professional service control scripts with detailed examples.

14 min read

When you're writing Bash scripts, you'll often need to handle multiple conditions and reuse code blocks. That's where case statements and functions come in. This comprehensive guide will teach you how to build robust, maintainable scripts using these powerful features.

๐Ÿ’ก

๐ŸŽฏ What You'll Learn: In this hands-on tutorial, you'll discover:

  • How to use case statements for multi-condition logic
  • Creating service control scripts with case statements
  • Defining and calling functions in Bash
  • Passing parameters to functions
  • Building conditional logic inside functions
  • Real-world examples with complete explanations
  • Best practices for script organization

๐ŸŽฏ Case Statements: Elegant Multi-Condition Logic

Case statements provide a cleaner, more readable alternative to multiple if-elif-else chains. They're perfect when you need to compare a single variable against multiple values.

Why Use Case Statements?

Instead of writing this:

if [ "$1" = "start" ]; then
    echo "Starting..."
elif [ "$1" = "stop" ]; then
    echo "Stopping..."
elif [ "$1" = "status" ]; then
    echo "Checking status..."
else
    echo "Invalid option"
fi

You can write this:

case $1 in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    status)
        echo "Checking status..."
        ;;
    *)
        echo "Invalid option"
        ;;
esac

Much cleaner and easier to read!

๐Ÿš€ Building a Service Control Script

Let's build a practical script that simulates controlling a system service. This pattern is used in many real-world Linux init scripts.

Step 1: Create the Script File

First, create a new script file:

nano case.sh

Step 2: Write the Service Control Script

Here's our complete service control script:

#!/bin/bash

if [ -z "$1" ]; then
	echo "Usage: $0 {start|stop|status}"
	exit 1
fi

case $1 in
	start)
		echo "starting the service..."
		;;
	stop)
		echo "stopping the service..."
		;;
	status)
		echo "checking the service status..."
		;;
	*)
		echo "Invalid command"
		;;
esac

Understanding the Script Line by Line

Let's break down each component:

The Shebang Line

#!/bin/bash

Purpose: Tells the system to use Bash to interpret this script.

What it does: When you run ./case.sh, the system reads this line and knows to execute the script using /bin/bash.

Checking for Arguments

if [ -z "$1" ]; then
	echo "Usage: $0 {start|stop|status}"
	exit 1
fi

Purpose: Validates that the user provided at least one argument.

Breaking it down:

  • [ -z "$1" ] - The -z test checks if the string is empty (zero length)
  • $1 - Represents the first command-line argument
  • echo "Usage: $0 {start|stop|status}" - Shows how to use the script correctly
  • $0 - Special variable containing the script name itself
  • exit 1 - Exits the script with error code 1 (non-zero indicates failure)
๐Ÿ’ก

๐Ÿ’ก Understanding -z Test: The -z flag returns true if the string is empty. So [ -z "$1" ] means "if the first argument is empty/missing".

The Case Statement Structure

case $1 in
	pattern1)
		commands
		;;
	pattern2)
		commands
		;;
	*)
		default commands
		;;
esac

Syntax breakdown:

  • case $1 in - Start the case statement, examining variable $1
  • pattern) - Pattern to match (like "start", "stop", etc.)
  • ;; - Marks the end of a pattern's commands (like break in other languages)
  • *) - Catch-all pattern (matches anything not matched above)
  • esac - Closes the case statement (it's "case" spelled backwards!)

Individual Case Patterns

start)
	echo "starting the service..."
	;;

Purpose: Matches when $1 equals "start".

What happens: When a user runs ./case.sh start, this block executes and displays "starting the service...".

stop)
	echo "stopping the service..."
	;;

Purpose: Matches when $1 equals "stop".

status)
	echo "checking the service status..."
	;;

Purpose: Matches when $1 equals "status".

The Default Case

*)
	echo "Invalid command"
	;;

Purpose: Catches any input that doesn't match the specific patterns above.

What it does: Acts as the "else" clause - if the user enters anything other than "start", "stop", or "status", this executes.

Step 3: Make the Script Executable

chmod +x case.sh

What this does: The chmod +x command adds execute permission to the file, allowing you to run it as ./case.sh.

Verify the permissions:

ls -l

Output:

total 4
-rwxr-xr-x. 1 centos9 centos9 278 Oct  3 23:25 case.sh

Understanding the output:

  • -rwxr-xr-x - The x characters indicate execute permission
  • First rwx - Owner permissions (read, write, execute)
  • Second r-x - Group permissions (read, execute)
  • Third r-x - Other users permissions (read, execute)

๐Ÿงช Testing the Service Control Script

Let's test all possible scenarios:

Test 1: Running Without Arguments

./case.sh

Output:

Usage: ./case.sh {start|stop|status}

What happened: The script detected no arguments ($1 was empty) and displayed the usage message.

Why it matters: Good scripts always provide usage help when called incorrectly.

Test 2: Starting the Service

./case.sh start

Output:

starting the service...

What happened:

  1. The script received "start" as $1
  2. The case statement matched the start) pattern
  3. The corresponding command executed

Test 3: Stopping the Service

./case.sh stop

Output:

stopping the service...

What happened: The stop) pattern matched and its command block executed.

Test 4: Checking Service Status

./case.sh status

Output:

checking the service status...

What happened: The status) pattern matched and executed.

Test 5: Invalid Command

./case.sh invalid

Output:

Invalid command

What happened:

  1. "invalid" didn't match any specific pattern
  2. The wildcard pattern *) caught it
  3. The error message displayed

๐Ÿ“Š Case Statement Reference Table

ComponentSyntaxPurpose
Case openingcase $variable inStarts the case statement
Patternpattern)Value to match against
Pattern end;;Marks end of pattern block
Default pattern*)Matches anything (like else)
Case closingesacEnds the case statement

๐Ÿ”ง Functions: Reusable Code Blocks

Functions let you define a block of code once and call it multiple times. This makes your scripts more organized, maintainable, and follows the DRY (Don't Repeat Yourself) principle.

Basic Function Syntax

function_name() {
    commands
}

Or with the function keyword (both are valid):

function function_name {
    commands
}

๐Ÿ“ Creating Your First Function

Let's create a simple greeting function:

Step 1: Create the Script

nano greet.sh

Step 2: Define a Simple Function

#!/bin/bash

# Define function
print_greeting() {
	echo "Hello Sir!"
}

print_greeting

Understanding this script:

Function Definition

print_greeting() {
	echo "Hello Sir!"
}

Purpose: Defines a function named print_greeting.

Components:

  • print_greeting() - Function name followed by parentheses
  • { - Opening brace marks the start of function body
  • echo "Hello Sir!" - The command(s) to execute
  • } - Closing brace marks the end of function body
๐Ÿ’ก

๐Ÿ’ก Function Naming: Use descriptive names with underscores for readability. print_greeting is clearer than pg or greet.

Function Call

print_greeting

Purpose: Executes the function.

What happens: When the script reaches this line, it jumps to the function definition, executes the code inside, then returns here.

Step 3: Make Executable and Test

chmod +x greet.sh
./greet.sh

Output:

Hello Sir!

What happened:

  1. Script starts executing
  2. Bash reads the function definition (doesn't execute it yet)
  3. Script reaches print_greeting call
  4. Execution jumps to function body
  5. echo "Hello Sir!" runs
  6. Execution returns after function completes

๐ŸŽ Functions with Parameters

Functions become much more powerful when they can accept parameters. Let's enhance our greeting function.

Accepting a Single Parameter

Update greet.sh:

#!/bin/bash

# Define function with parameter
print_greeting() {
	echo "Hello, $1!"
}

print_greeting $1

What changed:

Function Definition with Parameter

print_greeting() {
	echo "Hello, $1!"
}

Purpose: The function now uses $1 to access its first parameter.

Important distinction:

  • Inside the function, $1 refers to the function's first argument
  • Outside the function, $1 refers to the script's first argument

Passing Parameter to Function

print_greeting $1

Purpose: Calls the function and passes the script's first argument ($1) to the function.

Data flow:

  1. User runs ./greet.sh Alice
  2. Script's $1 = "Alice"
  3. print_greeting $1 calls function with "Alice"
  4. Inside function, function's $1 = "Alice"

Test with Parameter

./greet.sh

Output:

Hello, !

What happened: No argument provided, so $1 is empty.

./greet.sh Alice

Output:

Hello, Alice!

What happened: "Alice" was passed through and displayed in the greeting.

๐ŸŽฏ Functions with Multiple Parameters

Let's add conditional logic based on a second parameter:

#!/bin/bash

# Define function with multiple parameters
print_greeting() {
	if [ $2 == "morning"  ]; then
		echo "Good morning, $1"
	else
		echo "Hello $1"
	fi
}

print_greeting $1 $2

Understanding the enhanced function:

Conditional Logic in Function

if [ $2 == "morning"  ]; then
	echo "Good morning, $1"
else
	echo "Hello $1"
fi

Purpose: Provides different greetings based on the second parameter.

How it works:

  • $1 - First function parameter (the name)
  • $2 - Second function parameter (time of day)
  • [ $2 == "morning" ] - Tests if second parameter equals "morning"
  • If true: prints "Good morning"
  • If false: prints generic "Hello"

Calling with Two Parameters

print_greeting $1 $2

Purpose: Passes both script arguments to the function.

Testing Multiple Parameters

./greet.sh Alice morning

Output:

Good morning, Alice

Data flow:

  1. Script receives: $1 = "Alice", $2 = "morning"
  2. Function called with both values
  3. Inside function: $1 = "Alice", $2 = "morning"
  4. Condition $2 == "morning" is true
  5. "Good morning, Alice" is printed
./greet.sh Alice

Output:

./greet.sh: line 5: [: ==: unary operator expected
Hello Alice

What happened:

  • Only one argument provided
  • $2 is empty inside the function
  • Test [ $2 == "morning" ] fails because $2 is unset
  • The script shows an error but continues, executing the else block
โš ๏ธ

โš ๏ธ Important: This error occurs because we're testing an empty variable. In the next part of this series, we'll learn proper error handling to prevent these issues.

๐Ÿ“Š Function Parameter Reference

VariableContextMeaning
$0ScriptScript name
$1Outside functionFirst script argument
$1Inside functionFirst function argument
$#BothNumber of arguments
$@BothAll arguments as separate words
$*BothAll arguments as single word

๐ŸŽฏ Best Practices

โœ… Case Statements

  1. Always include a default case: Use *) to handle unexpected input
  2. Provide usage messages: Help users understand valid options
  3. Use meaningful patterns: Choose clear, descriptive pattern names
  4. Validate input first: Check for required arguments before the case statement
  5. Use exit codes: Return 0 for success, non-zero for errors
  6. Keep it simple: If you have too many cases, consider splitting functionality

โœ… Functions

  1. Use descriptive names: calculate_total is better than calc
  2. One purpose per function: Each function should do one thing well
  3. Validate parameters: Check that required parameters are provided
  4. Quote variables: Always use "$1" not $1 to handle spaces
  5. Return status codes: Use return 0 or return 1 to indicate success/failure
  6. Comment your functions: Explain what parameters they expect
  7. Define before calling: Always define functions before you call them in the script

๐Ÿ“ Command Cheat Sheet

Case Statement Syntax

# Basic case statement
case $variable in
    pattern1)
        commands
        ;;
    pattern2)
        commands
        ;;
    *)
        default commands
        ;;
esac

# Multiple patterns (OR logic)
case $variable in
    pattern1|pattern2)
        commands
        ;;
esac

# Pattern matching with wildcards
case $variable in
    *.txt)
        echo "Text file"
        ;;
    *.jpg|*.png)
        echo "Image file"
        ;;
esac

Function Syntax

# Define function (method 1)
function_name() {
    commands
}

# Define function (method 2)
function function_name {
    commands
}

# Function with parameters
greet() {
    echo "Hello, $1!"
}
greet "Alice"

# Function with return value
check_file() {
    if [ -f "$1" ]; then
        return 0  # Success
    else
        return 1  # Failure
    fi
}

# Using function return value
if check_file "test.txt"; then
    echo "File exists"
fi

# Function with local variables
calculate() {
    local result=$(( $1 + $2 ))
    echo $result
}

Common Test Operators

# String tests
-z "$var"      # True if string is empty
-n "$var"      # True if string is not empty
"$a" = "$b"    # True if strings are equal
"$a" != "$b"   # True if strings are not equal

# File tests
-f "$file"     # True if file exists and is regular file
-d "$dir"      # True if directory exists
-e "$path"     # True if path exists (file or directory)
-r "$file"     # True if file is readable
-w "$file"     # True if file is writable
-x "$file"     # True if file is executable

๐Ÿš€ What's Next?

๐Ÿ“š Continue Learning

In Part 2, we'll cover:

  • Working with Bash arrays
  • Understanding the @ symbol and array expansion
  • Command substitution with $()
  • Capturing output from system commands
  • Building dynamic scripts with command substitution
  • Practical examples combining arrays and substitution

Stay tuned for the next guide!


โœ…

๐ŸŽ‰ Congratulations! You've mastered Bash case statements and functions. You can now write cleaner, more maintainable scripts with proper control flow and code organization.

What did you think of this guide? Share your questions or your own case statement and function examples in the comments below!

๐Ÿ’ฌ Discussion

I'd love to hear about your experience:

  • What types of scripts are you building with case statements?
  • Have you created any useful utility functions?
  • What challenges did you face with parameters?
  • What advanced topics would you like covered in future posts?

Connect with me:

  • ๐Ÿ™ GitHub - Example scripts and more
  • ๐Ÿ“ง Contact - Bash scripting questions and discussions
Owais

Written by Owais

I'm an AIOps Engineer with a passion for AI, Operating Systems, Cloud, and Securityโ€”sharing insights that matter in today's tech world.

I completed the UK's Eduqual Level 6 Diploma in AIOps from Al Nafi International College, a globally recognized program that's changing careers worldwide. This diploma is:

  • โœ… Available online in 17+ languages
  • โœ… Includes free student visa guidance for Master's programs in Computer Science fields across the UK, USA, Canada, and more
  • โœ… Comes with job placement support and a 90-day success plan once you land a role
  • โœ… Offers a 1-year internship experience letter while you studyโ€”all with no hidden costs

It's not just a diplomaโ€”it's a career accelerator.

๐Ÿ‘‰ Start your journey today with a 7-day free trial

Related Articles

Continue exploring with these handpicked articles that complement what you just read

More Reading

One more article you might find interesting