DevOps Peach

Linux · Bash Scripting · Lesson 4 of 5

Loops and case statements

Iterate predictably and branch with shell patterns.

Practice interactively →

for loops

for iterates over words or uses arithmetic syntax. {1..5} works for a literal range; when the limit is a variable, use for ((i=1; i<=n; i++)) because brace expansion happens before parameter expansion. seq 1 "$n" is another option that prints a sequence.

for i in {1..3}; do printf '%s\n' "$i"; done
for f in /var/log/*.log; do echo "checking $f"; done
for host in web1 web2 db1; do ssh "$host" uptime; done

A glob in for gives one file name per iteration, even names with spaces, so it is safer than looping over $(ls). If nothing matches, the pattern itself is returned; check [[ -e $f ]] inside the loop.

flowchart LR
  L["for f in *.log<br/>each item"] --> B["Loop body<br/>gzip $f"]
  B --> N{"More items?"}
  N -- "yes" --> L
  N -- "no" --> D["done"]
  W["while read -r line<br/>until input ends"] --> B
  CA["case $1 in<br/>start or stop"] --> PAT["first matching<br/>pattern runs"]
  %% step L: for walks a fixed list, such as filenames from a glob.
  %% step B: The body runs once per item; break and continue control the loop.
  %% step N: The loop repeats until the list is used up.
  %% step W: while read -r processes input line by line, for files or command output.
  %% step CA: case compares a value against shell patterns.
  %% step PAT: Only the first matching branch runs; *) catches everything else.

while and until

while IFS= read -r line; do printf '%s\n' "$line"; done < file.txt
while IFS=, read -r name port; do echo "$name -> $port"; done < hosts.csv

IFS= preserves leading and trailing whitespace, while read -r preserves backslashes. Setting IFS=, splits each CSV line into fields. Redirection after done supplies input to the whole loop and avoids a pipeline subshell in many cases.

while repeats while a command succeeds; until repeats while it fails, which suits waiting:

until curl -sf http://localhost:8080/health; do sleep 2; done

Controlling a loop

break leaves the loop and continue skips to the next iteration. while true; do ...; sleep 60; done creates a simple polling loop; stop it with Ctrl+C. Always make sure a while loop can end, for example with a retry counter.

case

case matches shell patterns; each arm normally ends with ;;, and *) is the fallback:

case $action in
  start|up) echo starting ;;
  stop) echo stopping ;;
  *.conf) echo "config file" ;;
  *) echo unknown ;;
esac

case reads more clearly than a long if/elif chain when one value is compared against many options, and it is the usual way to handle a script's subcommands such as start, stop, and status.

Patterns use glob semantics, not regular expressions by default. Quote the tested value when appropriate, but do not quote a wildcard pattern if you want pattern matching.

Practice interactively →