Conditionals: if/elif/else#
if [[ "$status" == "active" ]]; then
echo "Service is running"
elif [[ "$status" == "stopped" ]]; then
echo "Service is stopped"
else
echo "Unknown status: $status"
fiThe semicolon before then replaces a newline — these are equivalent:
if [[ condition ]]; then command; fi
if [[ condition ]]
then
command
fiTest Commands: [[ ]] vs [ ]#
[[ ]] — Bash Extended Test (Preferred)#
| Category | Operator | Meaning |
|---|---|---|
| String | == | Equal |
| String | != | Not equal |
| String | =~ | Regex match |
| String | -z "$s" | String is empty |
| String | -n "$s" | String is non-empty |
| Numeric | -eq, -ne | Equal, not equal |
| Numeric | -lt, -le | Less than, less or equal |
| Numeric | -gt, -ge | Greater than, greater or equal |
| File | -f | Is regular file |
| File | -d | Is directory |
| File | -e | Exists (any type) |
| File | -r, -w, -x | Readable, writable, executable |
| File | -s | Exists and non-empty |
| Logic | && | AND |
| Logic | || | OR |
| Logic | ! | NOT |
[ ] vs [[ ]] Comparison#
| Feature | [ ] (POSIX) | [[ ]] (Bash) |
|---|---|---|
| Word splitting on vars | Yes (must quote!) | No |
| Glob expansion | Yes | No |
Regex (=~) | ❌ | ✅ |
Pattern matching (== with *) | ❌ | ✅ |
&&, || inside | ❌ | ✅ |
| Portability | All shells | Bash/Zsh only |
Rule: Use [[ ]] in bash scripts. Use [ ] only for POSIX portability.
Practical Examples#
# File checks
[[ -f "$config" ]] || die "Config not found: $config"
[[ -d "$output_dir" ]] || mkdir -p "$output_dir"
# String checks
[[ -z "$API_KEY" ]] && die "API_KEY not set"
[[ "$env" == "prod" ]] && echo "⚠️ Production!"
# Regex
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
# Pattern matching (glob)
[[ "$file" == *.tar.gz ]] && echo "Compressed archive"
[[ "$branch" == feature/* ]] && echo "Feature branch"
# Numeric
[[ $count -gt 0 ]] && echo "Has items"
# Combined
[[ -f "$file" && -r "$file" ]] && echo "Readable file"Case Statement#
For multi-branch string matching — cleaner than chained if/elif:
case "$command" in
start)
start_service
;;
stop)
stop_service
;;
restart|reload) # multiple patterns
stop_service
start_service
;;
*) # default (catch-all)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esacPattern Matching in Case#
case "$filename" in
*.tar.gz|*.tgz) tar xzf "$filename" ;;
*.zip) unzip "$filename" ;;
*.deb) dpkg -i "$filename" ;;
*) echo "Unknown format" >&2 ;;
esacLoops#
For Loop — Iterate Over a List#
# Over literal values
for color in red green blue; do
echo "$color"
done
# Over glob results
for file in *.log; do
[[ -f "$file" ]] || continue
echo "Processing: $file"
done
# Over array elements
servers=("web-01" "web-02" "db-01")
for server in "${servers[@]}"; do
ping -c 1 "$server" &>/dev/null && echo "$server: UP"
doneC-Style For Loop#
for ((i = 0; i < 10; i++)); do
echo "Iteration $i"
doneWhile Loop#
# Counter-based
count=0
while [[ $count -lt 5 ]]; do
echo "Count: $count"
((count++))
done
# Read file line by line (CORRECT way)
while IFS= read -r line; do
echo "Line: $line"
done < input.txt
# Infinite loop with break
while true; do
read -rp "Continue? (y/n): " answer
[[ "$answer" == "n" ]] && break
doneUntil Loop#
Runs until condition becomes true (opposite of while):
until ping -c 1 server.example.com &>/dev/null; do
echo "Waiting for server..."
sleep 5
done
echo "Server is up!"Loop Control#
# break — exit loop entirely
for i in {1..100}; do
[[ $i -gt 10 ]] && break
done
# continue — skip to next iteration
for file in *.log; do
[[ -s "$file" ]] || continue # skip empty files
process "$file"
doneReading Files in Loops#
The Correct Way#
while IFS= read -r line; do
echo "$line"
done < "$filename"| Component | Purpose |
|---|---|
IFS= | Don’t strip leading/trailing whitespace |
read -r | Don’t interpret backslashes |
< file | Redirect file to loop’s stdin |
Process CSV#
while IFS=',' read -r name age city; do
echo "$name is $age, lives in $city"
done < data.csvWhy NOT for line in $(cat file)#
# WRONG — breaks on spaces, expands globs
for line in $(cat file.txt); do ...
# "hello world" becomes TWO iterations: "hello" and "world"Short-Circuit Evaluation#
# AND — run second only if first succeeds
[[ -d "$dir" ]] && cd "$dir"
# OR — run second only if first fails
[[ -f "$config" ]] || die "Config missing"
cd "$dir" || exit 1
# Combined: try or die with message
mkdir -p "$output_dir" || { echo "Cannot create dir" >&2; exit 1; }Key Takeaways#
- Use
[[ ]]for all tests in bash — safer, more features - Case statements are cleaner than long if/elif chains for string matching
while IFS= read -ris the only correct way to read files line by line- Never use
for line in $(cat file)— it breaks on spaces and globs - Short-circuit (
&&,||) for concise one-liners - Always quote variables in conditions