When a Bash script needs to choose between several possible actions, case is often a much cleaner solution than a long chain of if / elif statements.
If you have programmed in other languages, you may recognize it as the Bash equivalent of a switch statement.
The basic idea is simple: Bash evaluates a value against a series of patterns. When a pattern matches, the corresponding commands are executed.
The general syntax looks like this:
case value in
pattern1)
cmds1
;;
pattern2)
cmds2
;;
*)
default_cmds
;;
esac
The case statement starts with case and ends with esac — which is simply case spelled backwards. Bash has apparently decided that symmetry is important.
Each pattern is followed by ), then the commands to execute, and finally ;; to mark the end of that branch.
The * pattern is commonly used as the default case because it matches anything that hasn't matched an earlier pattern.
A Real Example
Here's a small example that demonstrates how case can be used together with a loop:
while :
do
printf "Type . to finish ==> "
read line
case "$line" in
.)
echo "Message done"
break
;;
*)
echo "$line" >> "$message"
;;
esac
done
The while : creates an infinite loop. The : is Bash's null command, which always succeeds, so the loop continues until something explicitly breaks it.
Inside the loop, the script waits for the user to enter a line.
The case statement then checks what was entered:
case "$line" in
.)
...
;;
*)
...
;;
esac
If the user enters a single dot:
.
the first pattern matches:
.)
echo "Message done"
break
;;
The script prints:
Message done
and break terminates the loop.
For anything else, the * pattern matches:
*)
echo "$line" >> "$message"
;;
The entered text is appended to the file referenced by $message.
So the overall behavior is quite straightforward:
Type . to finish ==> Hello
Type . to finish ==> This is a message
Type . to finish ==> Another line
Type . to finish ==> .
Message done
The loop keeps accepting input until the user enters a single period. Everything else is treated as message content.
Why Use case?
You could implement the same logic with if and elif:
if [[ $line == "." ]]; then
echo "Message done"
break
else
echo "$line" >> "$message"
fi
For two possibilities, that is perfectly reasonable.
But as the number of alternatives grows, case becomes much easier to read:
case "$command" in
start)
start_service
;;
stop)
stop_service
;;
restart)
restart_service
;;
status)
show_status
;;
*)
echo "Unknown command: $command"
exit 1
;;
esac
This is one of the situations where case really shines: the possible values and their corresponding actions are visible at a glance.
Patterns, Not Just Exact Values
One important detail is that Bash case works with patterns, not just literal string comparisons.
For example:
case "$filename" in
*.log)
echo "Log file"
;;
*.conf)
echo "Configuration file"
;;
*.sh)
echo "Shell script"
;;
*)
echo "Unknown file type"
;;
esac
Here:
*.log
matches filenames ending in .log, while:
*.sh
matches shell scripts.
You can also combine multiple patterns in a single branch:
case "$environment" in
production|staging)
echo "Remote environment"
;;
development|testing)
echo "Non-production environment"
;;
*)
echo "Unknown environment"
;;
esac
This is often much cleaner than writing several separate conditions.
One More Useful Example
A common use for case is processing command-line arguments:
#!/usr/bin/env bash
case "${1:-}" in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
status)
echo "Checking service status..."
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
You could then run:
./service.sh start
or:
./service.sh status
An unknown command falls through to the * branch.
This pattern appears everywhere in shell scripting, from small utility scripts to much larger command-line tools.
A Small Syntax Detail Worth Remembering
Don't forget the ;; at the end of each branch:
case "$value" in
one)
echo "One"
;;
two)
echo "Two"
;;
esac
Unlike some languages, Bash does not use break to terminate an individual case branch. The ;; tells Bash that the commands belonging to that pattern are finished.
And unlike a traditional C-style switch, Bash does not normally fall through from one matching branch into the next.
case vs. if
As a general rule:
Use if when you're asking a question:
if [[ -f "$file" ]]; then
echo "File exists"
fi
Use case when you're choosing an action based on one value:
case "$action" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
esac
Of course, Bash doesn't enforce this distinction. You can solve the same problem in several ways. The goal is to choose the construct that makes the script easiest for the next human to understand; which may, unfortunately, be you six months from now.
Summary
The Bash case statement is the shell's answer to the classic switch construct found in many programming languages.
Its basic structure is:
case "$value" in
pattern1)
commands
;;
pattern2)
commands
;;
*)
default_commands
;;
esac
It is particularly useful when:
- you have several possible values;
- each value should trigger a different action;
- you want to use patterns such as
*.log; - several patterns should share the same action;
- you want command-line argument handling to remain readable.
For simple two-way decisions, if is often perfectly adequate. When your script starts looking like a wall of elif, however, it may be time to let case do the heavy lifting.
And, as always with Bash, remember: the shell may look simple right up until it isn't.