Bash Loops and Conditionals
Once you have variables, you need control flow to make decisions and repeat actions. Bash’s syntax is quirky but learnable. Below are the patterns you’ll actually use.
if / elif / else
# Test a condition
if [ "$count" -gt 10 ]; then
echo "big"
elif [ "$count" -gt 5 ]; then
echo "medium"
else
echo "small"
fi
# Modern bash: prefer [[ ]] over [ ]
if [[ "$file" == *.txt ]]; then
echo "is text file"
fi
Common test conditions
# Strings
[[ "$a" == "$b" ]] # equal
[[ "$a" != "$b" ]] # not equal
[[ -z "$a" ]] # empty
[[ -n "$a" ]] # not empty
[[ "$file" == *.log ]] # glob match
[[ "$str" =~ ^[0-9]+$ ]] # regex match
# Numbers
[[ $a -eq $b ]] # equal
[[ $a -ne $b ]] # not equal
[[ $a -lt $b ]] # less than
[[ $a -le $b ]] # less or equal
[[ $a -gt $b ]] # greater than
[[ $a -ge $b ]] # greater or equal
# Files
[[ -f file ]] # is regular file
[[ -d dir ]] # is directory
[[ -e path ]] # exists
[[ -r file ]] # readable
[[ -w file ]] # writable
[[ -x file ]] # executable
[[ -s file ]] # exists and non-empty
# Combine
[[ -f "$file" && -r "$file" ]]
[[ -d "$dir" || -f "$file" ]]
for loops
# Loop over a list
for fruit in apple banana cherry; do
echo "$fruit"
done
# Loop over files (use glob, NOT command substitution)
for f in *.txt; do
wc -l "$f"
done
# Loop over an array
fruits=(apple banana cherry)
for f in "${fruits[@]}"; do
echo "$f"
done
# C-style for
for ((i=0; i<10; i++)); do
echo "$i"
done
# Loop over a range
for i in {1..10}; do
echo "$i"
done
# With step
for i in {0..100..10}; do
echo "$i"
done
while and until
# While condition is true
count=0
while [[ $count -lt 5 ]]; do
echo "$count"
((count++))
done
# Until condition is true
until ping -c1 server.com &>/dev/null; do
echo "waiting for server..."
sleep 5
done
# Read each line of a file
while IFS= read -r line; do
echo "line: $line"
done < input.txt
# Read from a command (process substitution)
while IFS= read -r line; do
echo "got: $line"
done < <(grep "ERROR" log.txt)
case (the cleaner switch)
case "$1" in
start)
echo "starting..."
;;
stop)
echo "stopping..."
;;
restart|reload)
echo "restarting..."
;;
*.log)
echo "log file: $1"
;;
*)
echo "unknown: $1"
exit 1
;;
esac
Functions
greet() {
local name="$1" # local variable; not visible outside
echo "Hello, $name"
}
greet "alice"
greet "bob"
# Return a value via stdout
get_user_count() {
wc -l /dev/null
return $?
}
if is_running nginx; then
echo "yes"
fi
Loop control
for f in *.txt; do
[[ -s "$f" ]] || continue # skip empty files
[[ "$f" == "skip.txt" ]] && continue
if [[ "$f" == "stop.txt" ]]; then
break # exit the loop
fi
process "$f"
done
Real script template
#!/usr/bin/env bash
set -euo pipefail # exit on error, on undefined var, on pipe fail
usage() {
cat <&2; exit 1; }
# Real work here
[[ $verbose -eq 1 ]] && echo "processing $file"
wc -l "$file"
Common mistakes
for f in $(ls)breaks on filenames with spaces. Usefor f in *.- Forgetting
;then,;do— bash will hang waiting for more input. [ ]with==isn’t portable. Use=in[ ], or use[[ ]]for==.- Reading a file with
cat file | while read— the loop runs in a subshell, variables don’t survive. Usewhile read; done < file.
What to learn next
When your scripts get bigger, you need debugging tools — set -x, set -e, traps. Up next.