Section 1 of 13

1 Before you start

Before you begin, take a few minutes to check what you already know and how confident you feel. You will see the same questions again at the end of the module — this helps both you and us measure what you have learned. Click an option for every question and confidence rating, then click Next to continue.

Pre-test

Which keyword closes both for and while loops in bash?

Pre-test

What does for i in {1..5} expand to?

Pre-test

Which idiom reads a file one line at a time?

Pre-test

Inside a for loop, what does continue do?

Pre-test

What is $((count + 1))?

Pre-test

What does break do inside a loop?

Pre-test

Which loop keyword runs the body as long as a condition stays TRUE?

Pre-test

What does for f in *.csv; do ... done loop over?

Pre-confidence

I can write a for loop that processes every file matching a pattern.

Not at all confident
Fully confident
Pre-confidence

I can write a while loop that reads a file line by line.

Not at all confident
Fully confident
Pre-confidence

I can use break and continue to control how a loop runs.

Not at all confident
Fully confident
Section 2 of 13

2 Introduction

If you ever find yourself running the same command twice with a small change, it is time to use a loop. Bash loops are simple, slightly quirky, and the bread-and-butter of automation. By the end of this lesson you will be able to process a folder full of patient files without typing each filename, and you will know how to read a file one line at a time.

  • for loops over a list of words, files, or numbers.
  • while loops while a condition is true.
  • until loops until a condition becomes true (rarely used).
  • The classic pattern: while read line; do ... done < file.
A for loop binds a variable to each item in a list and runs the same command body once per item, so a single written command processes an entire set of files without retyping.
A for loop binds a variable to each item in a list and runs the same command body once per item, so a single written command processes an entire set of files without retyping.
Section 3 of 13

3 for Loops - The Workhorse

Every for loop in bash relies on three magic keywords: for, do, and done. The concept is simple: you provide a list of items, and the script runs your commands once for every item in that list.

Section 3.1 of 13

3.1 Looping over lists

Let's say we have three medical codes we need to process. We define a temporary variable (like code) that will hold one value at a time as it steps through the list.

for code in I10 E11 J45
do
    echo "Searching for code $code"
done

Notice the semicolon (;) before the word do? That is required if you want to put do on the exact same line as the for statement. If you find semicolons hard to remember, you can drop it by simply putting do on its own line:

for code in I10 E11 J45
do
    echo "Searching for code $code"
done
A for loop assigns each list element to the loop variable in turn and executes the loop body once per element.
A for loop assigns each list element to the loop variable in turn and executes the loop body once per element.
Section 3.2 of 13

3.2 Looping over numbers

If you just need to repeat something a specific number of times, you do not have to type out 1 2 3 4 5. You can use bash's brace expansion {start..end}.

for i in {1..5}
do
    echo "Visit $i"
done

You can even add a "step" to skip numbers using {start..end..step}:

for i in {2..20..2}
do
    echo $i
done # 2, 4, 6, ..., 20
Brace expansion rewrites {start..end..step} into an explicit list of values before the command runs, and the optional step sets the stride, keeping some values and skipping the rest.
Brace expansion rewrites {start..end..step} into an explicit list of values before the command runs, and the optional step sets the stride, keeping some values and skipping the rest.

Brace expansion only works with hardcoded numbers. If you need to count up to a variable, like $max_visits, you will use a command called seq instead: for i in $(seq 1 $max_visits); do.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
for i in {1..5}
do
    echo "Visit $i"
done
Bash performs brace expansion before variable expansion, so a brace range built from a variable cannot expand because the number only appears at the later stage, by which point the braces are already literal text.
Bash performs brace expansion before variable expansion, so a brace range built from a variable cannot expand because the number only appears at the later stage, by which point the braces are already literal text.
Section 3.3 of 13

3.3 Looping over files

In the real world, you will spend most of your time looping over files in a directory. You can use the * wildcard to quickly grab everything that matches a pattern.

for patient in vitals/patient_*.txt
do
    echo "Processing $patient"
done

The shell is smart enough to expand vitals/patient_*.txt into a full list of matching files before the loop even starts running. If there are five matching files, the loop runs five times.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
for f in vitals/patient_*.txt
do
    n=$(wc -l < "$f")
    echo "$f has $n lines"
done
A shell glob expands into the complete list of matching files once, before the loop begins, so the number of matches fixes the number of iterations.
A shell glob expands into the complete list of matching files once, before the loop begins, so the number of matches fixes the number of iterations.
Section 4 of 13

4 When You Need a Counter

Sometimes you don't have a list of files; you just need a literal numeric counter. Bash has a special double-parenthesis syntax (( ... )) that lets you do math and counting.

for (( i=0
i<10
i++ ))
do
    echo "Iteration number: $i"
done

This loop is built in three distinct steps:

  • Initialize: i=0 (Start counting at zero).
  • Condition: i<10 (Keep going as long as i is strictly less than 10).
  • Step: i++ (Add exactly 1 to i after every loop).
A C-style bash for loop runs its initialiser once, then before each pass it tests the condition, executes the body, and applies the step, repeating until the condition becomes false.
A C-style bash for loop runs its initialiser once, then before each pass it tests the condition, executes the body, and applies the step, repeating until the condition becomes false.
Section 5 of 13

5 while Loops - Loop While a Condition Holds

A while loop doesn't count. Instead, it runs continuously as long as a specific condition remains true.

count=0
while [[ $count -lt 5 ]]
do
    echo "count is $count"
    count=$((count + 1))
done

We use the [[ ... ]] brackets to test our condition (just like an if statement). Inside the loop, we use $(( ... )) to actually do the math. Whenever you wrap an equation in $(( )), bash evaluates it as numbers rather than text, allowing you to use standard math operators like +, -, *, and /.

A while loop repeats its body only for as long as its condition stays true and stops the moment the condition becomes false, with [[ ]] testing the condition and $(( )) evaluating the arithmetic.
A while loop repeats its body only for as long as its condition stays true and stops the moment the condition becomes false, with [[ ]] testing the condition and $(( )) evaluating the arithmetic.
Section 5.1 of 13

5.1 The most useful while pattern - reading a file line by line

If you only memorize one piece of code from this lesson, make it this one. This is the standard way to process a file one line at a time in bash.

while IFS= read -r patient_id
do
    echo "processing: $patient_id"
done < patient_ids.txt

It looks a bit crowded, but each tiny piece is there to prevent a specific kind of bug:

  • IFS= : This temporarily clears the "Internal Field Separator." In plain English, it stops bash from accidentally deleting leading or trailing spaces from your lines.
  • read : The command that actually grabs the next line of text.
  • -r : Stands for "raw." It stops bash from trying to interpret backslashes (\) as special escape characters.
  • < patient_ids.txt - This feeds your file into the bottom of the loop so read can consume it line by line.

You will use this exact block of code constantly whenever you have a list of URLs, file paths, or sample IDs stored in a text file.

Try it out

Try this snippet in the Bash Scratchpad on the right.

Try this snippet
while IFS= read -r line
do
    echo "note: $line"
done < notes.txt
A while IFS= read -r loop consumes a text file one line at a time, binding each line to a variable for the loop body to act on, repeating until end of file.
A while IFS= read -r loop consumes a text file one line at a time, binding each line to a variable for the loop body to act on, repeating until end of file.
Section 6 of 13

6 break and continue

Sometimes you need to interrupt a loop halfway through.

  • break - exit the current loop immediately.
  • continue - skip the rest of this iteration, jump to the next.
for n in 1 2 3 4 5
do
    if [[ $n -eq 3 ]]
then
        continue
    fi
    if [[ $n -eq 5 ]]
then
        break
    fi
    echo $n
done
# prints 1 2 4
continue skips the rest of the current iteration and moves to the next value, while break terminates the loop entirely, so the values after a continue still run but nothing after a break does.
continue skips the rest of the current iteration and moves to the next value, while break terminates the loop entirely, so the values after a continue still run but nothing after a break does.
Section 7 of 13

7 Putting It Together - Process a Folder of Patient Files

Let's combine everything into a real-world scenario. Imagine you have a folder full of patient vital sign files, and you want to count how many records (lines) are in each file, saving the results to a neat .tsv report.

# count lines in every vitals file in the folder
for f in vitals/patient_*.txt
do
    base=$(basename "$f" .txt)
    n=$(wc -l < "$f")
    echo "$base $n"
done > line_counts.tsv

Walk through it slowly: We loop over the files, use basename to strip away the messy folder paths and .txt extensions, use wc -l to count the lines, and then spit out a clean row. By redirecting the output of the entire loop using > line_counts.tsv, we generate a complete data report in just 5 lines of code.

A single redirect placed after done collects every iteration of a for loop into one file, so looping over files with basename and wc -l yields one tidy .tsv report rather than one file per pass.
A single redirect placed after done collects every iteration of a for loop into one file, so looping over files with basename and wc -l yields one tidy .tsv report rather than one file per pass.
Section 8 of 13

8 Check your understanding

You have reached the end of the module. Try the same questions again — your answers here, paired with your pre-test answers, are how we measure what the module taught you. Answer every question and confidence rating, then click Submit and see results to view your score.

Post-test

Which keyword closes both for and while loops in bash?

Post-test

What does for i in {1..5} expand to?

Post-test

Which idiom reads a file one line at a time?

Post-test

Inside a for loop, what does continue do?

Post-test

What is $((count + 1))?

Post-test

What does break do inside a loop?

Post-test

Which loop keyword runs the body as long as a condition stays TRUE?

Post-test

What does for f in *.csv; do ... done loop over?

Post-confidence

I can write a for loop that processes every file matching a pattern.

Not at all confident
Fully confident
Post-confidence

I can write a while loop that reads a file line by line.

Not at all confident
Fully confident
Post-confidence

I can use break and continue to control how a loop runs.

Not at all confident
Fully confident
Section 9 of 13

9 Your results

Here is how your post-test answers compare with your pre-test answers. The pre/post pairing is the most reliable way to see what this module actually taught you.

Your score

Submit the post-test to see your results.

Muddiest point

What is the one thing from this module that is still unclear to you?

Rate this module

Overall, how would you rate this module?

How likely are you to recommend this module to a peer? (0 = not at all, 10 = extremely likely)