Linux Basics: Repeat Work with Bash Loops
Loops repeat a small block of commands. Beyond greeting a list of arguments, common jobs include bounded calculations, reading complete lines, and stopping when a condition is met.
These examples were tested on Linux with GNU Bash 5.2.37. Use Bash rather than sh; some syntax is Bash-specific. No packages or administrator access are needed.
Prepare a scratch directory
You need Bash, a text editor, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. The guards stop the shell if setup fails. Save each named source file in this directory before running its command. Calling bash explicitly means you do not need to change executable permissions.
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 11. Repeat a bounded calculation
Save this as loop-count.sh:
#!/usr/bin/env bash
total=0
for (( number=1; number<=4; number++ )); do
total=$((total + number))
printf 'After %s: %s\n' "$number" "$total"
doneRun it:
bash loop-count.shOutput:
After 1: 1
After 2: 3
After 3: 6
After 4: 10The arithmetic for form has an initializer, a condition, and an update. It checks the condition before each iteration. A clear bound makes this useful for small, known counts without starting an external sequence command.
2. Read lines without losing spaces
Save this as loop-lines.sh:
#!/usr/bin/env bash
printf ' first item \nsecond\\item' > lines.txt
count=0
while IFS= read -r line || [[ -n "$line" ]]; do
count=$((count + 1))
printf '%s: <%s>\n' "$count" "$line"
done < lines.txt
printf 'Lines: %s\n' "$count"Run it:
bash loop-lines.shOutput:
1: < first item >
2: <second\item>
Lines: 2IFS= preserves leading and trailing whitespace. read -r keeps backslashes literal. The extra condition processes the last nonempty line even without a final newline. Redirecting a file into the loop keeps its counter in the current shell; a pipeline can put the loop in a subshell. This script creates only lines.txt in the scratch directory.
3. Skip an item and stop early
Save this as loop-control.sh:
#!/usr/bin/env bash
for item in draft skip ready stop later; do
if [[ "$item" == skip ]]; then
continue
fi
if [[ "$item" == stop ]]; then
break
fi
printf 'Process: %s\n' "$item"
done
printf 'Loop finished\n'Run it:
bash loop-control.shOutput:
Process: draft
Process: ready
Loop finishedcontinue skips the rest of the current iteration. break exits the loop, so later is never visited. Both affect the innermost loop by default. Keep the stopping rule visible to avoid accidental unbounded work.
Clean up
In the same terminal, remove only the example files, return to your original directory, and remove the empty scratch directory:
rm -- loop-count.sh loop-lines.sh loop-control.sh lines.txt
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirReference
The GNU Bash manual: Looping Constructs documents the syntax used here.
Acknowledgements
This post was created with assistance from AI.