Learn › RHCSA (EX200) › Shell Scripts
for - a hands-on Linux lab on a real virtual machine.
Run the same block once for every item with a for loop. The fixed for VAR in LIST; do ...; done shape, and four lists that feed it: an explicit set of words, a .txt glob, command output through $( ), and a script's arguments through "$@". Run a command per item with wc in the body. Serves EX200 task 9: loop over arguments or command output and emit one formatted line per item.
A task on the exam hands you a folder of files and says: do the same thing to every one of them. Rename each, or count the lines in each, or back each up. There might be three files. There might be forty. Typing the command once per file is slow, and the moment you copy and paste you will miss one.
The shell has a tool built for exactly this: hand it a list, and it runs the same block of commands once for every item in that list. It is the for loop, and it is the workhorse of EX200 task 9, loop over a set of things and emit one clean line per item. By the end of this lesson you will loop over a plain list, over a folder of files, over the output of another command, and over the arguments a script was given.
The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The outputs shown are a real capture from a RHEL 10 machine (AlmaLinux 10.2). The example files and accounts on your own machine will differ, but the SHAPE of a for loop and the four ways to feed it a list are what carry to the exam.
A for loop takes a list of items and runs a block of code once for each one. Each time through, the current item is stored in a variable you name, so the body of the loop can act on it.
The shape never changes. You write it on one line at the prompt, separating the parts with semicolons:
for VAR in LIST; do COMMANDS; done
Read it in plain English: for each VAR in this LIST, do these commands, and when the list runs out, you are done. The three keywords for, do, and done are fixed. The VAR is any name you pick, and the LIST is whatever you want to iterate over. Everything else in this lesson is just a different way to build that LIST.
Start with the simplest list there is: words you type out yourself. Put three items after in, and the loop body runs three times. Inside the body you read the current item back with a $ in front of the variable name, so $n gives you whatever n holds on this pass.
Loop over the numbers 1, 2, and 3, and print a line for each:
for n in 1 2 3; do echo "line $n"; done
prompt: student@servera:~$ answer: for n in 1 2 3; do echo "line $n"; done output: line 1 line 2 line 3 hint: The shape is for VAR in LIST; do ...; done. Put 1 2 3 as the list and echo "line $n" as the body.
Three passes, three lines. On the first pass n held 1, so echo "line $n" printed line 1; on the second it held 2, and on the third 3. The double quotes matter: they let the shell swap $n for its value while keeping the word line literal. That is the entire mechanic of a for loop. Everything from here just changes where the list comes from.
You rarely type the list by hand. More often it is a set of files, and the shell builds that list for you with a glob. A glob is a wildcard pattern: *.txt expands to every file in the current directory whose name ends in .txt, in sorted order. Drop that pattern straight after in and the loop walks each matching file.
Imagine the current directory holds a.txt and b.txt. Loop over every .txt file and announce each one:
for f in *.txt; do echo "file: $f"; done
prompt: student@servera:~$ answer: for f in *.txt; do echo "file: $f"; done output: file: a.txt file: b.txt hint: The list is the glob *.txt. The body echoes "file: $f" for the file held in f each pass.
The shell expanded *.txt into a.txt b.txt before the loop even started, so for f in *.txt became for f in a.txt b.txt. Each pass, f held one filename, and "file: $f" printed it. This is the form to prefer on the exam: a bare glob handles filenames cleanly and, unlike wrapping ls in a loop, it does not spawn an extra program.
Your directory holds different files, so your lines will read differently. There is one honest wrinkle: if NO file matches the glob, the shell leaves the pattern untouched and the loop runs once with the literal text *.txt as the value. When that would break a script, guard the body with [ -e "$f" ] || continue to skip a non-existent entry.
The most powerful list is one you compute on the spot. Wrap a command in $( ), command substitution, and the shell runs that command first and drops its output in as the list. The loop then iterates over each word of that output.
Say a command produces three usernames, alice, bob, and carol. Capture that output as the list and label each one. Here printf stands in for whatever command generates the names:
for u in $(printf 'alice\nbob\ncarol\n'); do echo "user=$u"; done
prompt: student@servera:~$ answer: for u in $(printf 'alice\nbob\ncarol\n'); do echo "user=$u"; done output: user=alice user=bob user=carol hint: Put the command inside $( ) as the list. The classic exam form is $(cut -d: -f1 /etc/passwd) to pull usernames.
Three names came out of the command, so the loop ran three times, printing user=alice, user=bob, then user=carol. On the exam the command inside $( ) is usually a real one, $(cut -d: -f1 /etc/passwd) to pull every username out of the account file, for example. The loop treats each whitespace-separated word as one item. That word-splitting is why this form is perfect for single-word items like usernames and filenames without spaces, and why you avoid it for text that contains spaces.
Inside a script, the words the user typed after the script name are the positional parameters: $1, $2, and so on. The whole set of them is "$@", and that is the exam's favorite list to loop over. A backup script says back up each path I was given; you loop over "$@" and act on each one.
The quotes are not optional. "$@" keeps each argument as one item even if it contains a space, which is why it is the correct form and bare $@ is the buggy one. Picture a script whose body is for p in "$@"; do echo "path: $p"; done. Run it with three paths:
./backup.sh /etc /home /var
prompt: student@servera:~$ answer: ./backup.sh /etc /home /var output: path: /etc path: /home path: /var hint: Inside the script the loop is for p in "$@"; do echo "path: $p"; done. You are running the script here with three paths.
The script looped over "$@", which expanded to the three arguments you passed, and printed one path: line each. That is the shape of EX200 task 9: a script takes a list of arguments and emits one formatted line per item. Loop over "$@" with the quotes, and each argument stays intact even if it holds a space. This capture is illustrative; the paths you pass are the paths you get back.
So far the body has only echoed. The point of a loop is to DO work per item, and the body can be any command. The exam-classic move is to run a command against each file and report the result on one line. Count the lines of each .txt file with wc -l, feeding the file in so wc prints only the number:
for f in *.txt; do echo "$f has $(wc -l < "$f") lines"; done
prompt: student@servera:~$ answer: for f in *.txt; do echo "$f has $(wc -l < "$f") lines"; done output: a.txt has 3 lines b.txt has 1 lines hint: Loop the glob *.txt, and inside the echo use $(wc -l < "$f") to count the lines of the current file.
Two files, two lines of report. Each pass, f held one filename, and $(wc -l < "$f") ran wc against just that file. Feeding the file in with < instead of naming it makes wc print the bare count, 3 then 1, with no filename tagging along, so the sentence reads cleanly. That is command substitution from the last step nested inside the loop body: compute a value per item, then print it in a formatted line. On the exam this is how you turn a folder of files into a neat report.
Scaffolding off. No command is shown this time. You have every piece you need.
You are writing a script and this is the loop at its heart: for every argument the script was handed, print a line that reads Hello, followed by that argument. Think about which list holds all the arguments with their spaces preserved, and remember the three fixed keywords that frame every loop. Below, the script is run with three names.
prompt: student@servera:~$ answer: for name in "$@"; do echo "Hello, $name"; done output: hint: The list that holds every argument, quoted, is "$@". Build for name in "$@"; do echo "Hello, $name"; done. No output is captured here.
You reached for "$@", the quoted set of every argument, as the list, then framed the body with do and done and printed "Hello, $name" for the item held in name. Run inside a script with three names, it would print three Hello, lines, one per argument, in order. That is the exact skeleton EX200 task 9 grades: take the arguments a script was given and emit one formatted line per item. You built it from memory, which is the recall the real machine will ask of you.
You earned this cheat sheet. Every row is a list form you just ran or built:
The three keywords never change: for ... in LIST, then ; do, then the body, then done. Forget the do and the shell answers with syntax error near unexpected token 'done'. Forget the $ inside the body and you print the literal variable name instead of its value.
Prefer a glob (for f in *.txt) over looping on ls output (for f in $(ls)). The glob handles filenames correctly, keeps the order, and spawns no extra process. On the exam, for f in *.conf is almost always the right list for a folder of files.
The practice terminal has shown you the shape of the for loop and the four lists that feed it: an explicit set of words, a glob of files, the output of another command through $( ), and a script's own arguments through "$@". You also ran a real command per item, counting lines with wc inside the body. Every one of those you typed yourself.
The shell-scripts module mission is where you run these against a real RHEL 10 machine. A full VM boots for you, with its own files and its own accounts. The mission hands you objectives that use exactly what you practiced here: loop over a folder, loop over command output, loop over the arguments a script was handed, and emit one formatted line per item, which is EX200 task 9 itself. One difference from this lesson: the mission shows no commands. You read the objective, recall the loop, and type it. That recall is what makes it stick on exam day.
Finish the other shell-scripts lessons, then go automate a real machine for yourself.
Practice for Loops Over Files and Output in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.