Course / 03 Command line / Lesson 16 of 19

Bash variables, if, and a loop

Estimated time: 25 min reading · 25 min activity

You want to repeat the same small task for several values. A loop describes that repetition once. Before running it, follow each assigned value and separate the loop's output from any commands that run afterward.

A variable stores a value under a name. Expansion substitutes that value into a command. A loop repeats a body of commands, while a condition decides whether a command block runs. Bash also returns an exit status: zero conventionally means success, and nonzero describes another outcome. That number is separate from printed text.

Before you begin

Estimated time
25 min reading, 25 min activity. Setup and optional practice may take longer.
Where you work
Use Bash in your owned Ubuntu practice environment as an ordinary user. The supplied text files use UTF-8, a text encoding; where shown, LC_ALL=C requests consistent text ordering. Work only under your own new ~/yanosec-lab directory, never a host share or personal/system file. Refuse an existing exercise destination rather than overwriting it. No account, paid platform, or remote target is required. Optional extensions take about 10–15 minutes and are not part of the time above. A virtual machine is optional. You can also finish the reasoning from the printed evidence.
Without a lab
The page supplies the files, source and text records needed for the activity. Without a shell, follow the numbered steps on paper, label each result as predicted, and use the separate evidence supplied by the question. Running commands can wait until your lab is available. Reading works without JavaScript. Answer checking needs JavaScript and a secure context, such as HTTPS; saving a correct result also needs usable browser storage. Missing tools, managed or mobile hosts, or a deferred guest setup never require buying a service. Keep owned lab files for review; do not use broad deletion.

After this lesson you can:

  • Predict the argument produced by a quoted variable expansion.
  • Trace a three-item loop and a separate condition.
  • Count body executions in an independent fixed iteration list.

01Example

The variable value contains a space. Assigning it uses no spaces around =. Quoting its later expansion keeps the entire value in one argument for printf. Read the predicted output separately from the two commands.

Commands:
note_label='red blue'
printf '%s\n' "$note_label"

Predicted stdout, one line:
red blue

Without quotes, expansion may split words or expand filename patterns.
Quoting preserves argument boundaries; it does not authorize actions.

02Read one construct at a time

In note_label='red blue', the assignment has no spaces around =. The straight quotes keep the two words together as its value. Later, "$note_label" expands that value as one argument. Quoting an expansion preserves its argument boundary instead of allowing word splitting or filename expansion.

The loop for tone in amber violet silver; do ...; done assigns each listed value to tone and runs the body once per value. do starts the body and done ends it. A semicolon or newline separates shell commands. "$tone" passes the current value to printf as one argument.

The command if [ -n 'practice' ]; then ...; fi tests whether a string is nonempty. The [ command receives -n and the string as arguments, then a closing ] argument; those spaces matter. A successful test gives status zero, so the commands after then run. fi ends this condition.

A command after done is outside the loop. Its printed line does not create another iteration. Make one trace row per assignment, then a separate row for the later condition. Count body executions and output lines separately.

Use task-specific variable names. HOME and PATH already affect home-directory expansion and command lookup, so do not replace them for an exercise. The supplied script only prints text. Review source before using any later script that can change files or contact another system.

Quoted variable expansion preserves red blue as one argument, then printf produces one predicted output line.Model. A teaching model drawn by hand. It shows the idea, not real tool output.

Read the full explanation below.

Playback is optional. Step through at your own pace.

Instruction being read

note_label='red blue'

Saved variable value

note_label = red blue

assigned

printf value arguments

not yet passed

absent

Predicted stdout

not yet printed

unobserved

No spaces surround =; the assigned text includes the space in red blue.

Follow the explanation

  1. No spaces surround =; the assigned text includes the space in red blue.
  2. Quoted expansion passes one argument whose contents are red blue.
  3. The format prints that one value argument as one line.
Read every state and value

1. No spaces surround =; the assigned text includes the space in red blue.

Current command
note_label='red blue'
Data in each location
Pane: Saved variable value; Text: note_label = red blue; Status: assigned · Pane: printf value arguments; Text: not yet passed; Status: absent · Pane: Predicted stdout; Text: not yet printed; Status: unobserved
Links
From: note_label='red blue'; To: Saved variable value; Label: assign whole value

2. Quoted expansion passes one argument whose contents are red blue.

Current command
printf '%s\n' "$note_label"
Data in each location
Pane: Saved variable value; Text: note_label = red blue; Status: assigned · Pane: printf value arguments; Text: one value argument: red blue; Status: predicted · Pane: Predicted stdout; Text: not yet printed; Status: unobserved
Links
From: Saved variable value; To: printf value arguments; Label: quoted expansion: one argument

3. The format prints that one value argument as one line.

Current command
printf '%s\n' "$note_label"
Data in each location
Pane: Saved variable value; Text: note_label = red blue; Status: assigned · Pane: printf value arguments; Text: one value argument: red blue; Status: predicted · Pane: Predicted stdout; Text: red blue ; Status: predicted
Links
From: printf value arguments; To: Predicted stdout; Label: one formatted line
Quoted variable expansion preserves red blue as one argument, then printf produces one predicted output line.

03Try tracing then running

The complete script source supplies the activity evidence. It uses three tone values and then a separate conditional command. The accompanying transcript retains output recorded earlier with Bash 5.2.21; compare your prediction with that record without treating it as a new run.

On paper, mark each assignment, the line printed by the body, and the later nonempty test. If running it, use Bash explicitly after reading all source. UTF-8 describes the text encoding; LF means the Linux-style line ending. These details help avoid confusing a text-format problem with a permission problem.

#!/bin/bash
# Original harmless trace.
for tone in amber violet silver; do
  printf '%s\n' "$tone"
done
if [ -n 'practice' ]; then
  printf '%s\n' 'nonempty'
fi

Download the original harmless loop source

Trace the loop body separately from later commands

Read trace-loop.sh first. Make a row for each assigned tone value, then
a separate row for the condition after done. The record below retains
earlier output from Bash 5.2.21. Comparing your prediction with that
record does not mean you have executed the script yourself.

trace-loop.sh stdout observed with Bash 5.2.21:
amber
violet
silver
nonempty
Three loop iterations; the conditional line is outside the loop.
Separate question loop: for tone in red blue green white; do ...; done
Assume no nested loop, break, return or exit. Count body runs only.

Download the recorded output and separate question loop

  1. Read the complete trace-loop.sh source. Make a paper row for each tone assignment, its printed value and the conditional test after the loop. Expected: three body runs.
  2. Predict the separate conditional line. Expected: nonempty after amber, violet and silver because the test's string is nonempty. That fourth output line is not a fourth loop iteration.
  3. If doing live work, save the exact source as a UTF-8 file with LF newlines under your owned lab and run bash with its quoted actual path, such as bash ~/Downloads/trace-loop.sh. The command interprets the reviewed source; no execute-bit change is needed.
  4. Compare actual stdout with the four predicted lines. If you see a parsing issue, inspect quotes, newlines and which interpreter ran; do not add permissions or sudo to fix syntax.
  5. Keep the source and trace note. No background job, file write or remote action occurs, so execution needs no operational cleanup. Paper-only work is marked execution deferred.

04Optional challenge

Allow about ten extra minutes. Move the conditional inside the loop on paper. Predict each printed line in order and compare body count with output count. Explain which boundary changed; you do not need to edit or run another script.

05Fresh cases for the checks

Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.

Bash card A: a new quoted expansionOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

parcel_label='copper grey'
printf '%s\n' "$parcel_label"
Predict the value arguments after the format string, not the format string itself.
Source-reading case only; no execution is recorded.

Bash card B: a condition inside a loopOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

for badge in "" teal "" coral; do
  if [ -n "$badge" ]; then
    printf '%s\n' "$badge"
  fi
done
printf '%s\n' 'complete'
Each quoted empty string is a listed loop value. Predict stdout; no run is recorded.

Bash card C: independent body-count sourceOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

for stop_name in "north wing" central "south wing" annex courtyard; do
  printf '%s\n' "$stop_name"
done
printf '%s\n' 'route-end'
No nested loop, early exit or other control flow is present.

Bash card D: diagnose a changed expansionOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

room_label='east hall'
Original command: printf '%s\n' "$room_label"
Changed command:  printf '%s\n' $room_label
Assume ordinary Bash whitespace word splitting. The value contains no filename-pattern characters.
For this printf format, each resulting value argument is printed on its own line.
Only the quotes around the expansion are removed. Predict value arguments and stdout.

Bash card E: a separate changed conditionOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

stamp=''
if [ -n "$stamp" ]; then
  printf '%s\n' 'inside'
fi
printf '%s\n' 'closed'
Compared with a nonempty stamp, this card changes only its value to the empty string.

06Explain arguments, iterations and output

Optional self-check: explain why a multiword variable value can be one argument, why a loop visit need not print, and why a command after done does not add a loop iteration.

Review your explanation: connect double quotes to preserved argument boundaries; evaluate -n using the current string; mark do/done and then/fi boundaries; and distinguish value arguments, body runs and output lines. Keep predictions separate from any actual run. This explanation is self-reviewed.

07Common mistakes

If a spaced assignment is treated as a command, inspect syntax: note_label='red blue' has no spaces around =. Keep straight quotes and the task-specific variable name.

If a string containing spaces becomes several printed items, inspect whether expansion was quoted. Do not assume a variable's display determines its argument count.

If you count every output line as an iteration, locate do/done and inspect commands outside the body. The independent question loop states there is no early exit or nesting.

References

  • Ubuntu 24.04 packaged manual: bash(1)

    Shell grammar, quoting, builtins, redirection and job control; use only the sections introduced in this lesson. Use the named command and option sections. This manual describes Ubuntu 24.04 packages; your installed version can differ. The lesson supplies the facts needed for its activity.

  • Ubuntu 24.04 packaged manual: printf(1)

    Use the named command and option sections. This manual describes Ubuntu 24.04 packages; your installed version can differ. The lesson supplies the facts needed for its activity.