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 argumentecho "Usage: $0 {start|stop|status}"
- Shows how to use the script correctly$0
- Special variable containing the script name itselfexit 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 (likebreak
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
- Thex
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:
- The script received "start" as
$1
- The case statement matched the
start)
pattern - 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:
- "invalid" didn't match any specific pattern
- The wildcard pattern
*)
caught it - The error message displayed
๐ Case Statement Reference Table
Component | Syntax | Purpose |
---|---|---|
Case opening | case $variable in | Starts the case statement |
Pattern | pattern) | Value to match against |
Pattern end | ;; | Marks end of pattern block |
Default pattern | *) | Matches anything (like else) |
Case closing | esac | Ends 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 bodyecho "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:
- Script starts executing
- Bash reads the function definition (doesn't execute it yet)
- Script reaches
print_greeting
call - Execution jumps to function body
echo "Hello Sir!"
runs- 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:
- User runs
./greet.sh Alice
- Script's
$1
= "Alice" print_greeting $1
calls function with "Alice"- 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:
- Script receives:
$1
= "Alice",$2
= "morning" - Function called with both values
- Inside function:
$1
= "Alice",$2
= "morning" - Condition
$2 == "morning"
is true - "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
Variable | Context | Meaning |
---|---|---|
$0 | Script | Script name |
$1 | Outside function | First script argument |
$1 | Inside function | First function argument |
$# | Both | Number of arguments |
$@ | Both | All arguments as separate words |
$* | Both | All arguments as single word |
๐ฏ Best Practices
โ Case Statements
- Always include a default case: Use
*)
to handle unexpected input - Provide usage messages: Help users understand valid options
- Use meaningful patterns: Choose clear, descriptive pattern names
- Validate input first: Check for required arguments before the case statement
- Use exit codes: Return 0 for success, non-zero for errors
- Keep it simple: If you have too many cases, consider splitting functionality
โ Functions
- Use descriptive names:
calculate_total
is better thancalc
- One purpose per function: Each function should do one thing well
- Validate parameters: Check that required parameters are provided
- Quote variables: Always use
"$1"
not$1
to handle spaces - Return status codes: Use
return 0
orreturn 1
to indicate success/failure - Comment your functions: Explain what parameters they expect
- 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: