Conditions & Loops
Scripts become useful when they can make decisions ("is the disk nearly full?") and repeat work ("for every log file, compress it"). In Bash that means if and case for decisions, and for and while for loops.
This lesson covers the test syntax that confuses many beginners, the most useful checks on strings, numbers and files, and the safe way to read a file line by line. It ends with a small but real monitoring script.
if, elif and else
An if runs a command and checks whether it succeeded. The most common command to test with is [[ ]], Bash's test syntax:
marks=72
if [[ $marks -ge 90 ]]; then
echo "Grade A"
elif [[ $marks -ge 60 ]]; then
echo "Grade B"
else
echo "Needs improvement"
fiGrade B
Note the details: spaces inside the brackets are required ([[ $marks -ge 90 ]], not [[$marks -ge 90]]), then starts the block, and fi (if backwards) ends it. You can put then on its own line instead of after ;. Indenting the body by four spaces is not required, but makes scripts readable.
Tests: strings, numbers and files
Inside [[ ]], these operators are the ones you will use most:
| Test | True when |
|---|---|
$a == $b / $a != $b | Strings are equal / different (== also matches patterns such as k*) |
-z $a / -n $a | String is empty / not empty |
$a =~ regex | String matches a regular expression |
$x -eq $y, -ne, -lt, -le, -gt, -ge | Number comparisons: equal, not equal, less than, and so on |
-f path / -d path / -e path | Is a regular file / a directory / exists at all |
-r path, -w path, -x path | Readable / writable / executable by you |
-s path | File exists and is not empty |
!, &&, || | NOT, AND, OR |
For numbers, (( )) is often clearer, because it uses normal maths symbols: (( count > 10 )).
name="kabir"
[[ $name == k* ]] && echo "starts with k"
[[ -f orders.csv && -r orders.csv ]] && echo "orders.csv is a readable file"
[[ -d /nope ]] || echo "/nope is not a directory"
[[ "10" > "9" ]] && echo "text: 10 > 9" || echo "text: 10 < 9"
(( 10 > 9 )) && echo "number: 10 > 9"starts with k orders.csv is a readable file /nope is not a directory text: 10 < 9 number: 10 > 9
The short form test && command runs the command only if the test succeeds; test || command runs it only if the test fails. Watch the fourth line: inside [[ ]], > compares text, character by character, so "10" sorts before "9". Use -gt or (( )) for numbers.
if works with any command, not just tests. grep -q (quiet) is a favourite, because it succeeds only when it finds a match:
if grep -q "Kochi" orders.csv; then
echo "We have customers in Kochi"
ficase: choose between many values
When one value can match several patterns, case is clearer than a chain of elif:
action="restart"
case "$action" in
start|stop|restart) echo "Running: systemctl $action nginx" ;;
status) echo "Showing status" ;;
*) echo "Unknown action: $action" ;;
esacRunning: systemctl restart nginx
Each pattern ends with ), each branch ends with ;;, | separates alternatives, and * catches everything else. esac is case backwards. Patterns use wildcards, so *.jpg|*.png) matches image file names.
for loops
A for loop runs its body once for each item in a list. The list can be written out, come from brace expansion, or come from a wildcard:
for city in Pune Jaipur Kochi; do
echo "Shipping to $city"
done
for i in {1..3}; do printf "%s " "$i"; done; echo
for (( i = 10; i > 0; i -= 4 )); do printf "%s " "$i"; done; echo
for f in /var/log/*.log; do echo "log file: $f"; doneShipping to Pune Shipping to Jaipur Shipping to Kochi 1 2 3 10 6 2 log file: /var/log/alternatives.log log file: /var/log/auth.log log file: /var/log/dpkg.log
The third form is the C-style loop with a counter. Looping over a wildcard like *.log is the safe way to process files, because each name arrives as one item even if it contains spaces. Never loop over the output of ls.
while and until
A while loop repeats as long as its command succeeds; until repeats until it succeeds:
count=3
while (( count > 0 )); do
echo "T-minus $count"
(( count-- ))
done
until ping -c 1 -W 1 192.168.1.1 > /dev/null; do sleep 5; doneT-minus 3 T-minus 2 T-minus 1
The second loop waits, checking every five seconds, until the router answers. while true; do ...; done loops forever, which is common for simple watchers (stop with Ctrl+C).
Reading a file line by line
The standard pattern combines while with read. IFS=, splits each line on commas into the named variables, and -r keeps backslashes intact. continue skips to the next line and break leaves the loop:
total=0
while IFS=, read -r id customer city item amount; do
[[ $id == order_id ]] && continue
(( total += amount ))
if [[ $city == Lucknow ]]; then
echo "Stopping at first Lucknow order ($customer)"
break
fi
done < orders.csv
echo "Total so far: $total"Stopping at first Lucknow order (Rohan) Total so far: 2138
The file is fed into the loop with < orders.csv after done. This is better than cat orders.csv | while ..., because with a pipe the loop runs in a child shell and total would be lost when the loop ends.
A real script: disk space checker
Putting it together, this script warns when any filesystem is above 80% full. It reads df output line by line and uses parameter expansion to strip the % sign:
#!/usr/bin/env bash
# diskcheck.sh: warn when any filesystem is getting full
limit=80
df -P -x tmpfs | tail -n +2 | while read -r fs size used avail pcent mount; do
usage=${pcent%\%}
if (( usage >= limit )); then
echo "WARNING: $mount is ${usage}% full ($fs)"
else
echo "ok: $mount ${usage}%"
fi
doneok: / 25% ok: /boot 14% ok: /boot/efi 6% WARNING: /srv/data is 91% full (/dev/sdb)
df -P prints one line per filesystem in a fixed format that is easy to parse. Run it from cron or a systemd timer and send the output to a log, and you have basic monitoring.
Common mistakes
- Missing spaces in tests.
[[$a == $b]]is an error; write[[ $a == $b ]]. - Comparing numbers with
>or==inside[[ ]]. Those compare text. Use-gt,-eqor(( )). - Looping over
$(ls). File names with spaces break apart. Usefor f in *.txt. - Piping into
whileand expecting variables to survive. Usedone < fileinstead. - Forgetting
;;incase. Bash reports a syntax error near the next pattern.
Key points
if cmd; then ...; elif ...; else ...; firuns a branch based on whether a command succeeds.[[ ]]tests strings, files and patterns; use-eq/-gtor(( ))for numbers.casematches one value against several wildcard patterns.for x in list,for f in *.logandfor (( i=0; i<n; i++ ))repeat over items;whileanduntilrepeat on a condition.- Read files with
while IFS= read -r line; do ...; done < file; usebreakandcontinueto control loops.
Exercise
Write a script cleanup.sh that loops over every .log file in a folder given in the variable dir (default /tmp/applogs). For each file: skip it if it is empty, print "large" if it is bigger than 1 MB (use stat -c %s file for the size in bytes), otherwise print "small". At the end print how many files were checked.
Show solution
#!/usr/bin/env bash
# cleanup.sh: classify log files by size
dir="${dir:-/tmp/applogs}"
checked=0
for f in "$dir"/*.log; do
[[ -e $f ]] || continue
[[ -s $f ]] || { echo "skip (empty): $f"; continue; }
size=$(stat -c %s "$f")
if (( size > 1024 * 1024 )); then
echo "large: $f ($size bytes)"
else
echo "small: $f ($size bytes)"
fi
(( checked++ ))
done
echo "Checked $checked file(s)"large: /tmp/applogs/api.log (2306112 bytes) skip (empty): /tmp/applogs/cron.log small: /tmp/applogs/web.log (48211 bytes) Checked 2 file(s)
[[ -e $f ]] || continue handles a folder with no .log files, where the pattern would otherwise stay as the literal text *.log. -s is true only for non-empty files, and the empty file is skipped before it is counted.
Quick Quiz
1. Which test is true when report.txt exists and is a regular file?
-f checks for a regular file; -d checks for a directory.
2. Why does [[ "10" > "9" ]] evaluate as false?
Use -gt or (( 10 > 9 )) for numeric comparison.
3. What does ;; do in a case statement?
Each case branch ends with ;;.
4. What is the safe way to process every .txt file in a folder?
The wildcard gives each name as one item, even with spaces.
Learn Linux with a mentor
Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
