Conditionals & loops in bash
if, test and [[ ]], file tests, case, for, while and until loops, and reading files line by line.
Scripts become truly useful when they can decide ("if the backup failed, alert me") and repeat ("for every log file, compress it"). In this lesson you'll learn if, the test commands [ ] and [[ ]], case, and the for, while and until loops, including the right way to process files line by line.
if: decisions based on exit codes#
In bash, if doesn't evaluate a boolean expression. It runs a command and checks its exit status: 0 means true, anything else false.
So any command can be a condition. Full syntax: if ...; then ...; elif ...; then ...; else ...; fi.
Tests: [ ] and [[ ]]#
To compare values or check files, use a test command:
[ ... ]is the classic POSIXtestcommand. It works in every shell, includingsh/dash.[[ ... ]]is bash's improved version: safer with empty and unquoted variables, and it supports&&,||, pattern matching and regexes. Prefer it in bash scripts.
The spaces inside the brackets are required: [[ $x == y ]], not [[$x==y]].
String tests
Number tests
File tests
Combining conditions
Inside [[ ]] use &&, || and !. With the old [ ], chain separate tests instead: [ "$a" -gt 1 ] && [ "$b" = x ] (and always quote variables inside [ ]).
case: matching patterns#
case is cleaner than long if/elif chains when you're matching one value against patterns:
Each branch ends with ;;. Patterns use glob syntax (*, ?, [abc]), and | means "or". *) is the catch-all.
for loops#
Over a list of words:
Over files. Use a glob, never $(ls):
Quoting "$f" inside the loop body keeps names with spaces intact. If nothing matches, the glob stays as the literal text *.csv; shopt -s nullglob makes it expand to nothing instead.
Over a range of numbers, or C-style:
(Brace ranges can't use variables: {1..$n} doesn't work. Use for ((i=1; i<=n; i++)) or $(seq 1 "$n").)
Over arguments: for arg in "$@"; do ...; done, or just for arg; do ...; done.
while and until#
while repeats as long as a command succeeds; until repeats until it succeeds:
A realistic until waits for a service: until curl -fs http://localhost:8080/health; do sleep 2; done. Add a maximum number of tries so it can't loop forever.
Reading a file line by line#
The correct idiom is while IFS= read -r:
read -r host ip rolesplits each line on whitespace into fields; the last variable gets the rest of the line.- Use
while IFS= read -r lineto keep the whole line exactly, including leading spaces. < servers.txtgoes afterdone.- Avoid
for line in $(cat file): it splits on every space, not on lines.
Piping into while (cmd | while read ...) runs the loop in a subshell, so variables set inside are lost afterwards. Use done < <(cmd) (process substitution) when you need them:
break and continue#
Putting it together#
A disk-space checker that warns about any filesystem above a threshold:
Run it from cron and you get an alert whenever a disk crosses the line. (Your mount points will differ.)
Common mistakes#
- Missing spaces in tests:
[[$a == $b]]andif [ $a=$b ]are both wrong. - Using
=vs-eqwrongly:-eqis for integers, and==(or=) for strings. >and<inside[[ ]]compare strings, not numbers; inside[ ]they are redirections and create files!- Unquoted variables in
[ ]:[ $x = y ]errors when$xis empty. Use[[ ]]or quote it. for f in $(ls)orfor line in $(cat file)break on spaces. Use globs andwhile read.- Infinite
while/untilloops with no timeout or retry limit.
What's next#
Your scripts are getting longer, so next you'll organise them with functions, and make them fail safely with set -euo pipefail, trap and argument parsing.
Check your understanding
Quick quiz
1.Inside
[[ ]], how do you compare two numbers for 'greater than'?2.What is the safe way to read a file line by line in bash?
3.Which test is true if a path exists and is a directory?
Finished reading?
Mark this lesson complete to track your progress.