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. Use for 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. Use while read; done < file.

What to learn next

When your scripts get bigger, you need debugging tools — set -x, set -e, traps. Up next.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *