Exercise 5 of 10 · Loops
Photo Rename Plan
What you will make
Three lines showing how each photo would be renamed to its _small version, followed by three numbered contact-sheet page lines produced with seq.
The one new idea: for loops over a word list, and ${name} braces mark where a variable name ends
The for loop over a list of words is the workhorse of shell scripting: for every file, for every host, for every name. Its list is split on spaces like everything else, so quotes change how many times it runs. And the ${name} form is not decoration: it is the only way to put a variable right before a letter or underscore without the shell reading a different name.
Go straight to the code ↓Doing the same thing to each word
A for loop takes a list of words and runs its body once for each, with the variable set to that word:
for colour in red blue; do
echo "$colour"
doneThe list is just words separated by spaces, exactly as the shell splits any command line. That has a consequence: if you put quotes around the whole list, you have made it one word, and the loop runs once with the whole string. Quotes are for keeping things together — and here you want them apart.
The second thing this exercise teaches is a quirk of $name inside text. The shell reads a variable name as far as it can: letters, digits and underscores. So $pic_small means a variable called pic_small, not $pic followed by _small. When the next character could be part of a name, wrap the name in braces: ${pic}_small. The braces mark exactly where the name ends.
seq prints a run of numbers, one per line, and $(seq 1 3) drops them into a loop's list.
A worked example
for colour in red blue; do
echo "${colour}_paint.txt"
done
for i in $(seq 1 3); do
echo "coat $i"
donered_paint.txt
blue_paint.txt
coat 1
coat 2
coat 3The first loop needs the braces, because _paint would otherwise be swallowed into the variable name and the line would print just .txt. The second loop's list came from seq.
Your turn
The editor holds a plan for shrinking three holiday photos: for each name it should print a line like name.jpg -> name_small.jpg, and then a numbered list of three contact-sheet pages.
Two lines are marked. The for line has quotes around the whole list, so the loop runs once with all three names glued together. The echo line uses $pic_small, which is a variable that does not exist, so the right-hand side comes out as .jpg with no name in front of it.
Take the quotes off the list and add braces around pic, then run it. You should see three rename lines followed by three page lines.
If something goes wrong
If the first loop prints a single line with all the names in it, the list is still quoted.
If the right-hand side of the arrow is just .jpg, the shell is looking for a variable named pic_small. Write ${pic}_small so the name stops at pic.
If you see syntax error near unexpected token, check that the loop still has its do and done.
Write your code
Runs in your browser. Press Run (or Ctrl/Cmd+Enter) and the output is checked for you.
Press Esc then Tab to move keyboard focus out of the code editor.
Output will appear here after you run your code.The runtime is starting in the background. You can type now — it will be ready before you are.
The answer appears here once you have run your code at least once.
Things that often go wrong here
- Quoting the whole list in the for line
- Quotes glue text into one word, and a for loop runs once per word. With the quotes on, the loop runs a single time with pic set to all three names at once.
- Writing $pic_small and expecting $pic followed by _small
- Letters, digits and underscores are all legal in a variable name, so the shell reads pic_small as the name. It is unset, so it expands to nothing. Braces, ${pic}_small, tell the shell exactly where the name stops.
- Adding braces on the left too and changing the text by accident
- ${pic}.jpg is perfectly fine and prints the same as $pic.jpg, so adding braces there does no harm. Just be careful not to change any other character while doing it, because the grader compares every one.
- Quoting $(seq 1 3) in the second loop
- "$(seq 1 3)" is one word containing three lines, so the loop would run once and print a three-line page number. The command substitution has to be unquoted here so its output is split into separate words.
Want a blank editor instead? Open the Bash & the Shell playground.