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" # readyRemember
- 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'.