Lesson: Conditionals & Tests
What you'll learn
- How to branch your script with
if,elif, andelse. - How to write tests with
[[ ]]for strings, numbers, and files. - How exit codes drive conditionals, and how to chain commands with
&&and||. - How to handle many possible values cleanly with
case.
By the end you can make scripts that react to their environment — checking whether a file exists, whether a service is up, or whether a value is what you expect.
The lesson
Real automation has to make decisions: if the disk is full, warn; if the service is down, restart it. In Bash, decisions are built on exit codes (from the last lesson) and the if statement.
Run everything on the Jumpbox (10.100.100.254, user ubuntu).
1. if is driven by exit codes
An if statement runs a command and branches on its exit code: 0 (success) is "true", non-zero is "false". This surprises newcomers — there is no boolean type, just exit codes.
if grep -q "ubuntu" /etc/passwd; then
echo "user ubuntu exists"
else
echo "no such user"
fi
grep -q prints nothing but exits 0 if it found a match. The structure is always:
if <command>; then
<runs when command succeeds / exit 0>
elif <other command>; then
<runs when that one succeeds>
else
<runs when none succeeded>
fi
Note fi (if backwards) closes the block, and the then needs a ; or a newline before it.
2. The test command [[ ]]
Most of the time you are not testing a real command but a condition — is this number bigger, does this file exist? Bash gives you [[ ... ]] for that. It is itself a command that exits 0 (true) or 1 (false):
count=5
if [[ $count -gt 3 ]]; then
echo "more than three"
fi
You may also see single-bracket [ ... ] (the older test command). Prefer [[ ]] in Bash: it is safer with empty variables, supports &&/|| inside, and does pattern matching. Always put spaces inside the brackets — [[$count is an error.
3. String tests
name="ubuntu"
[[ $name == "ubuntu" ]] # equal
[[ $name != "root" ]] # not equal
[[ -z $name ]] # true if string is EMPTY (zero length)
[[ -n $name ]] # true if string is NOT empty
[[ $name == ub* ]] # pattern match: starts with "ub"
Inside [[ ]], the right side of == is treated as a glob pattern unless quoted. So [[ $f == *.log ]] is true for any name ending in .log. Quote it ("*.log") to match literally.
A very common safety check:
read -rp "Enter a username: " u
if [[ -z $u ]]; then
echo "You must enter a name" >&2
exit 1
fi
4. Number tests
Numeric comparisons use letter operators, not symbols:
-eq equal -ne not equal
-gt greater than -lt less than
-ge greater or equal -le less or equal
disk_used=82
if [[ $disk_used -ge 90 ]]; then
echo "CRITICAL: disk over 90%"
elif [[ $disk_used -ge 75 ]]; then
echo "WARNING: disk getting full"
else
echo "Disk OK"
fi
Do not use > or < for numbers inside [[ ]] — there they mean string ordering, not numeric. Use -gt, -lt, etc. (For arithmetic you can also use (( )): if (( disk_used >= 90 )); then.)
5. File tests
These are the bread and butter of sysadmin scripts:
-e PATH exists (file or directory)
-f PATH exists AND is a regular file
-d PATH exists AND is a directory
-r PATH is readable -w writable -x executable
-s PATH exists AND is non-empty
config="/etc/hosts"
if [[ -f $config && -r $config ]]; then
echo "$config is a readable file"
else
echo "Cannot read $config" >&2
exit 1
fi
Notice && inside [[ ]] means "both conditions true". You can also use || for "either", and ! for "not": if [[ ! -d /backups ]]; then mkdir /backups; fi.
6. Chaining with && and || (outside brackets)
Between whole commands, && and || are shortcuts based on exit codes:
mkdir -p /tmp/work && echo "ready" # echo runs ONLY if mkdir succeeded
ping -c1 example.com || echo "host down" # echo runs ONLY if ping failed
A && BrunsBonly ifAsucceeded (exit 0).A || BrunsBonly ifAfailed (non-zero).
This is a concise alternative to a full if for one-liners. A handy pattern for "do this or bail out":
cd /var/log || exit 1
If cd fails, the script stops instead of running the rest in the wrong directory — a real bug-preventer.
7. case for many options
When you are comparing one value against many possibilities, a chain of elif gets ugly. case is cleaner:
read -rp "Action (start/stop/status): " action
case "$action" in
start)
echo "starting..." ;;
stop)
echo "stopping..." ;;
status)
echo "checking..." ;;
*)
echo "Unknown action: $action" >&2
exit 1 ;;
esac
Each branch is a pattern, then ), then commands, ending in ;;. The *) at the end is the catch-all (default), like else. Patterns are globs, so you can match *.log) or y|Y|yes) for several values at once. case closes with esac (case backwards).
8. Putting it together
#!/usr/bin/env bash
# Warn if a log directory is missing or its disk usage is high.
logdir="/var/log"
if [[ ! -d $logdir ]]; then
echo "No $logdir directory" >&2
exit 1
fi
used=$(df --output=pcent "$logdir" | tail -1 | tr -dc '0-9')
if [[ $used -ge 90 ]]; then
echo "CRITICAL: $logdir at ${used}%"
exit 2
else
echo "$logdir usage ${used}% — OK"
fi
This checks a file condition, computes a number from a command, and branches on it — the shape of the health-check you will build in the assignment.
Dig deeper
- Bash Manual: Conditional Constructs
- Bash Manual: Bash Conditional Expressions (test operators)
- if statements in Bash (Linux Handbook)
- Bash Manual — Conditional Expressions (
[[ ]]test operators) - TLDP — The case statement (Bash Beginners Guide)
Search terms
bash if statement exit code true falsebash double bracket test string comparisonbash numeric comparison -gt -lt -eqbash file test -f -d -e operatorsbash && || short circuit command chainingbash case statement esac example
Check yourself
- In an
if, what exit code counts as "true" and which counts as "false"? - Why is
[[ ]]preferred over single-bracket[ ]in Bash? - Which operator tests whether a string is empty, and which tests numeric "greater than"?
- What is the difference between
A && BandA || B? - In a
casestatement, what does the*)branch do, and what closes the whole block?
No comments to display
No comments to display