Recall from lesson 8-2 that for a script called as ./tag.sh a.txt b.txt c.txt, the variable holding all the arguments at once is "$@".
The neighbors each hold something narrower: $# is just the count, which is 3 here, $0 is the script name, and $! is the last background PID from lesson 6-2. This lesson is about walking through lists like "$@" one item at a time.
Do it once per item
Loops are where the terminal starts beating any amount of clicking. Converting 200 images, creating a directory per chapter, checking every log file: each of those is one loop, written in seconds.
A for loop repeats a block of commands once for each item in a list:
for fruit in apple banana cherry; do echo "I packed: $fruit" done
The anatomy is for VARIABLE in LIST; do COMMANDS; done. On each round the variable takes the next value from the list, and the body runs again with that new value.
The real power comes from where the list can originate. It might be a wildcard from lesson 4-2 as in for f in *.txt, the script's own arguments as in for arg in "$@", or plain numbers as in for n in 1 2 3.
Looping over words, then over matching files
Two loops of the same shape with different lists. The first walks three literal words, and the second walks whatever *.txt expands to, which quietly excludes three.md.
for fruit in apple banana cherry; do echo "I packed: $fruit" done touch one.txt two.txt three.md for f in *.txt; do echo "found $f" done
Output
I packed: apple I packed: banana I packed: cherry found one.txt found two.txt
The second loop ran twice, not three times, because the shell expanded *.txt into one.txt two.txt before the loop started, exactly as lesson 4-2 described. The loop never sees a wildcard, only the finished list of names, which is why three.md was never a candidate.
Number and letter ranges: brace expansion
Typing 1 2 3 4 5 by hand does not scale. Brace expansion generates the list instead: before running the command, bash rewrites {1..5} into 1 2 3 4 5, and {a..c} into a b c. As with the wildcards in lesson 4-2, this is the shell rewriting text before the command runs, so the loop never sees braces at all, only the finished list.
It works outside loops too. mkdir chapter{1..9} creates nine directories in one command, and cp app.conf{,.bak} expands to cp app.conf app.conf.bak, which is a much-loved backup idiom.
Numeric and alphabetic ranges
Two loops driven by brace ranges, one counting numbers and one stepping through letters.
for n in {1..4}; do echo "room $n cleaned" done for letter in {a..c}; do echo "section $letter" done
Output
room 1 cleaned room 2 cleaned room 3 cleaned room 4 cleaned section a section b section c
The loop bodies contain no numbers or limits of their own, which is the point worth absorbing. Widening the first range to {2..10} would change how many times the body runs without requiring a single edit inside it, because the range produces the list and the body only handles one item.
In a directory holding four .log files and two .txt files, the loop for x in *.log; do echo hi; done runs its body 4 times.
The wildcard *.log expands to just the four matching names, so the list has four items and the body runs once per item. The two .txt files never enter the list, and nothing about the loop body affects the count.
A countdown puts a loop and a follow-up line side by side. The list is written in descending order, and the final echo sits outside the loop so it runs exactly once.
for n in 5 4 3 2 1; do echo "T-minus $n" done echo "liftoff"
Output
T-minus 5 T-minus 4 T-minus 3 T-minus 2 T-minus 1 liftoff
A for loop takes its list in whatever order it is given, so counting down needs nothing more than listing the numbers backwards. The placement of the last echo after done is what keeps liftoff from printing five times.