Bash Conditionals: if Statements, Test Operators, and case
Edited by Dev Kilaru, Senior Data Analyst. How we make our content
Introduction
Scripts become useful when they can decide what to do. Bash decides by looking at exit codes, the same zero-for-success numbers you met in the first lesson. An if statement runs a command and branches on whether it succeeded, and the test commands let you ask questions about strings, numbers, and files. This lesson covers if and elif, the double-bracket test, numeric and file tests, combining conditions, and the case statement for choosing between many options.
Prerequisites
- Variables and quoting from the previous lesson
- A Bash shell and a text editor
Step-by-Step Guide
Step 1: Branch with if, elif, and else
An if statement runs the command after the if keyword and checks its exit status. If the status is zero the then block runs, otherwise Bash tries each elif in turn and finally the else block. The statement ends with fi, which is if spelled backwards. Because the condition is just a command, you can test any program directly, such as grep with its quiet option, without looking at its output.
if grep -q "error" app.log; then
echo "Errors found"
elif [[ -s app.log ]]; then
echo "Log has content but no errors"
else
echo "Log is empty or missing"
fiStep 2: Test strings with double brackets
The double-bracket test is the recommended way to compare values in Bash. Inside it you can leave variables unquoted without word splitting problems, compare text with the double equals sign, match a glob pattern on the right side, or match a regular expression with the equals-tilde operator. The older single-bracket form is the POSIX version that also works in plain sh, but it is stricter: variables must be quoted and the spaces inside the brackets are mandatory in both forms.
name="Ada"
if [[ $name == "Ada" ]]; then echo "match"; fi
if [[ -z $name ]]; then echo "empty"; fi # -z empty, -n not empty
if [[ $name == A* ]]; then echo "starts with A"; fi # glob pattern, unquoted
if [[ $name =~ ^[A-Z][a-z]+$ ]]; then echo "looks like a name"; fi # regex
# POSIX form, also works in sh, so quote the variable:
if [ "$name" = "Ada" ]; then echo "match"; fiStep 3: Compare numbers correctly
Numbers have their own operators: -eq for equal, -ne for not equal, -lt and -le for less than and less than or equal, and -gt and -ge for greater. A friendlier option is the double-parentheses arithmetic test, which lets you write normal comparison symbols. Be careful not to use the less-than and greater-than symbols inside double brackets for numbers, because there they compare text alphabetically, and the string 9 sorts after the string 10.
count=12
if [[ $count -gt 10 ]]; then echo "more than 10"; fi
if (( count > 10 && count < 20 )); then echo "between 10 and 20"; fi
[[ 9 < 10 ]] && echo "true" || echo "false" # false: this is a text comparison
(( 9 < 10 )) && echo "true" # true: this is a numeric comparisonStep 4: Check files and directories
File tests answer questions that scripts ask constantly: does a file exist, is it a regular file or a directory, can I read or write it, and is it empty. Checking before you act lets your script print a clear message instead of failing in the middle of a job. Remember that the tests describe the moment you checked, so another process could change the file a moment later.
file="/etc/hosts"
[[ -e $file ]] && echo "exists"
[[ -f $file ]] && echo "regular file"
[[ -d /tmp ]] && echo "directory"
[[ -r $file ]] && echo "readable"
[[ -w $file ]] && echo "writable"
[[ -s $file ]] && echo "not empty"
[[ -x /usr/bin/env ]] && echo "executable"Step 5: Combine conditions
Inside double brackets, && means and, || means or, and an exclamation mark negates a test. Between commands, the same operators chain actions: the command after && runs only when the previous one succeeded, and the command after || runs only when it failed. The pattern cd dir || exit 1 is a common guard that stops a script instead of continuing in the wrong folder. Avoid using a && b || c as a substitute for if and else, because c also runs when b fails.
if [[ -f $file && -r $file ]]; then echo "readable file"; fi
if [[ ! -d backup ]]; then mkdir backup; fi
mkdir -p build && cd build # cd only runs if mkdir worked
cd /some/dir || exit 1 # stop the script if cd fails
# Pitfall: this is not a real if and else
[[ -f $file ]] && echo "found" || echo "missing"
# "missing" also prints if the echo of "found" failsStep 6: Choose between many options with case
The case statement compares one value against several patterns, which is cleaner than a long chain of elif lines. Each branch ends with a double semicolon, patterns can be combined with a vertical bar, and a lone asterisk pattern acts as the default. It is the standard way to handle commands like start, stop, and restart, and to validate yes or no answers.
read -r -p "Continue? (y/n) " answer
case "${answer,,}" in # ${var,,} lowercases the value (Bash 4 and newer)
y|yes) echo "Continuing" ;;
n|no) echo "Stopping"; exit 0 ;;
*) echo "Please answer y or n" ;;
esac
case "$1" in
start) echo "Starting service" ;;
stop) echo "Stopping service" ;;
restart) echo "Restarting service" ;;
*) echo "Usage: $0 {start|stop|restart}" >&2; exit 1 ;;
esacStep 7: Use commands directly as conditions
Because if looks at exit codes, you can test any command without brackets. Redirect the output to /dev/null when you only care about success or failure. An exclamation mark in front of a command reverses its result. The command -v check is the portable way to find out whether a program is installed, and it is the right first step for scripts that depend on other tools.
if ping -c 1 example.com > /dev/null 2>&1; then
echo "Network is up"
else
echo "Network is down"
fi
if ! command -v git > /dev/null; then
echo "git is not installed" >&2
exit 1
fiStep 8: Put it together: a disk space check
This small script combines arguments with a default value, command substitution, awk for picking a column, and a numeric test. The df command with the -P option prints in a stable POSIX format, so the fifth column of the second line is the usage percentage on both Linux and macOS. The script prints a warning to standard error and exits with status 1 when usage crosses the threshold, which lets cron or a monitoring tool notice the failure.
#!/usr/bin/env bash
# disk-check.sh - warn when the root filesystem is nearly full
threshold=${1:-80}
usage=$(df -P / | awk 'NR==2 { gsub("%", "", $5); print $5 }')
if (( usage >= threshold )); then
echo "WARNING: / is ${usage}% full (limit ${threshold}%)" >&2
exit 1
else
echo "OK: / is ${usage}% full"
fiFAQs
- What is the difference between [ ] and [[ ]]?
- The single bracket is a regular command from the POSIX standard, so its arguments go through word splitting and must be quoted. The double bracket is a Bash keyword with its own parsing, which allows unquoted variables, pattern matching, regular expressions, and && and || inside. Use double brackets in Bash scripts and single brackets only when you need portability to sh.
- Why does my numeric comparison give the wrong answer?
- Most often a less-than or greater-than symbol is used inside double brackets, where it compares text. Use -lt and -gt there, or use the double-parentheses arithmetic form, which compares as numbers.
- How do I check that a variable is set and not empty?
- Use [[ -n $var ]] to test for a non-empty value. If the script uses set -u, use [[ -n ${var:-} ]] so an unset variable does not cause an error. To make a value required, the colon-question parameter expansion stops the script with a message.
Summary
You can now branch with if, elif, and else, test strings, numbers, and files, combine conditions with and, or, and not, use commands directly as conditions, and choose between many options with case. The disk check script shows how these pieces turn into something you could schedule. Next you will repeat work with loops, store lists in arrays, and organize code into functions.
Related tutorials
- Schedule and Debug Shell Scripts: cron, bash -x, and Portability
- Shell Scripting Project: A Backup Script with Rotation and a Log Report
- Writing Safe Bash Scripts: set -euo pipefail, getopts, and trap
- Shell Pipes, Redirection, grep, sed, and awk for Text Processing
- Bash Loops, Arrays, and Functions Explained
Comments
Log in or create a free account to join the discussion.