for and while loops

Bash · Essentials · 10 min

Loops let one script process every file, server or line instead of one at a time.

The for-in loop iterates over words: for x in a b c; do ...; done. The list is usually a glob (*.txt), a quoted array ("${files[@]}") or brace expansion ({1..10}, {a,b,c}). Iterating over $(ls) is a classic mistake: file names with spaces get split; use a glob instead.

If a glob matches nothing, bash leaves the pattern itself as the single word (*.txt), so guard with [[ -e $f ]] || continue, or enable shopt -s nullglob to make it expand to nothing.

The C-style loop for ((i = 0; i < n; i++)) is handy for counters. while repeats as long as its condition command succeeds, and until repeats until it succeeds, which suits polling: until curl -fs localhost:8080/health; do sleep 1; done.

Inside any loop, break leaves it and continue jumps to the next iteration; both accept a number to affect enclosing loops. A loop is a compound command, so you can redirect or pipe it as a whole: done > out.txt writes everything the loop prints into one file, opened once.

Example

cd "$(mktemp -d)" && touch a.txt b.txt
for fruit in apple "star fruit" kiwi; do
  echo "- $fruit"
done                         # - apple / - star fruit / - kiwi
for f in *.txt; do
  [[ -e $f ]] || continue    # no match would leave the literal '*.txt'
  echo "found $f"
done                         # found a.txt / found b.txt
for ((i = 1; i <= 3; i++)); do printf 'i=%d ' "$i"; done; echo   # i=1 i=2 i=3
for n in {1..5}; do printf '%s ' "$n"; done; echo               # 1 2 3 4 5
count=0
while (( count < 3 )); do
  count=$(( count + 1 ))
done
echo "count=$count"          # count=3
until [[ -f ready ]]; do touch ready; done && echo "ready"      # ready

Remember

  • for x in words / glob / "${arr[@]}"; for ((;;)) for counters
  • while repeats on success, until repeats on failure
  • Iterate over globs, never over $(ls)

Watch out for

  • Forgetting that a non-matching glob stays as the literal pattern, so the loop runs once with '*.txt'.

Check yourself

  1. In an empty directory, how many times does `for f in *.log; do echo "$f"; done` print, by default?
  2. Which loop keeps retrying a command until it succeeds?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Looping-Constructs