Prerequisites#
- Modules M3–M5 (paths, listing, creating files)
- A bash shell (
echo $SHELLshould mention bash)
Learning objectives#
By the end of this module you will be able to:
- Generate lists of names with brace expansion
- Expand your home directory with tilde expansion
- Use parameter expansion for variables (
$VAR,${VAR}) - Capture command output with command substitution (
$(…)) - Match filenames with pathname expansion (globs:
* ? []) - Locate a binary with
whichand nest it inside another command
The shell expands before the command runs#
When you type a line and press Enter, bash does several expansion steps, then runs the resulting words as a command. That is why echo *.txt can print many filenames — echo never saw the star; bash already replaced it.
We will walk the expansions you use every day, with demos under /tmp/linux-lab-pushpjeet.
mkdir -p /tmp/linux-lab-pushpjeet
cd /tmp/linux-lab-pushpjeet
Brace expansion {…}#
Braces generate strings before looking at the disk. They are great for creating several related names at once.
echo {dev,staging,prod}.conf
Real output:
dev.conf staging.conf prod.conf
Ranges work too:
echo {1..5}
echo {a..c}{1..2}
1 2 3 4 5
a1 a2 b1 b2 c1 c2
Create three files in one shot:
mkdir -p /tmp/linux-lab-pushpjeet/brace-demo
cd /tmp/linux-lab-pushpjeet/brace-demo
touch {alpha,beta,gamma}.txt
ls
alpha.txt
beta.txt
gamma.txt
Braces do not need matching files to exist. That is the difference from globs.
Tilde expansion ~#
~ expands to your home directory. ~username expands to that user’s home (when the account exists).
echo ~
Example lab output:
/home/box
On your machine you will see /home/yourname (or similar). Use ~ in paths:
ls ~/Documents # if that folder exists
echo "Home is $HOME" # $HOME is the same idea as ~
Parameter expansion $VAR and ${VAR}#
Variables hold text. You assign without a dollar sign and expand with one:
LAB=linux-lab-pushpjeet
echo "Lab name: $LAB"
echo "Length: ${#LAB}"
echo "Unset safe: ${MISSING:-default-value}"
Real output:
Lab name: linux-lab-pushpjeet
Length: 19
Unset safe: default-value
Use ${VAR} when the name sits next to other characters: ${LAB}_backup is clear; $LAB_backup would look for a variable named LAB_backup.
(We go deeper on variables in M10. Here the goal is recognising $… as an expansion.)
Command substitution $(…)#
Run a command and paste its output into the current line:
echo "Today is $(date +%Y-%m-%d)"
echo "Shell binary: $(which bash)"
Real output from the lab:
Today is 2026-09-26
Shell binary: /usr/bin/bash
Nesting is common. List details for the bash binary:
ls -l $(which bash)
Example (fields may vary slightly by distro):
-rwxr-xr-x 1 root root ... /usr/bin/bash
Older scripts use backticks `cmd`. Prefer $(cmd) — it nests cleanly.
Pathname expansion (globs)#
Globs match existing pathnames:
| Pattern | Matches |
|---|---|
* |
any string (including empty), within one directory level |
? |
exactly one character |
[abc] |
one character from the set |
[0-9] |
one digit in the range |
mkdir -p /tmp/linux-lab-pushpjeet/globs
cd /tmp/linux-lab-pushpjeet/globs
touch file1.txt file2.txt notes.md data.csv report2.log
ls *.txt
ls file?.txt
ls *[12]*
Real output:
file1.txt
file2.txt
file1.txt
file2.txt
file1.txt
file2.txt
report2.log
If nothing matches, bash usually leaves the pattern as a literal word (depending on options). That surprises beginners — if ls *.bak says “No such file”, the star did not expand.
which — where is this command?#
which bash
which ls
Useful when several tools share a name or when you want a full path for scripts and nesting: ls -l $(which echo).
Putting expansions together#
A practical one-liner mindset:
# Generate env-specific filenames, then create them
cd /tmp/linux-lab-pushpjeet
touch app-{dev,prod}.env
ls app-*.env
# Show date-stamped message
echo "Lab check at $(date +%H:%M) in $PWD"
Remember the rough order: braces first, then tilde, parameters, commands, and finally globs. You do not need to memorise the full POSIX list on day one — just know that the shell rewrites your line before the program starts.
Common mistakes#
- Expecting braces to filter files —
{a,b}always generates both strings; use globs to match what exists. - Quoting too early —
echo "*.txt"prints the star; quotes suppress globbing (more in M9). - Spaces around
=in assignment —LAB = valueis wrong; useLAB=value. - Confusing
$(…)with(…)— parentheses alone start a subshell; dollar-parentheses capture output. - Globs across directories —
*does not cross/unless you use**with special options (skip that for now).
Hands-on lab#
mkdir -p /tmp/linux-lab-pushpjeet/m8-lab
cd /tmp/linux-lab-pushpjeet/m8-lab
- Create
host-{web,db,cache}.cfgwith onetouchusing braces. Confirm withls. - Print your home directory with
echo ~and withecho $HOME. - Set
COURSE=linux-from-scratchand echoCourse=${COURSE}. - Print
Binary: $(which mkdir). - Create
a1.txt a2.txt b1.txtand list onlya?.txt.
Verify: ls host-*.cfg shows three files; ls a?.txt shows a1.txt and a2.txt.
Practice: check your understanding#
Multiple choice#
1. What does echo {a,b}.txt print (assuming default bash)?
- A. Only files that exist
- B.
a.txt b.txt - C.
{a,b}.txt - D. An error
Answer
B. Brace expansion generates both names whether or not the files exist.
2. Which form captures the output of date?
- A.
@(date) - B.
$(date) - C.
#{date} - D.
date$
Answer
B. $(date) is command substitution.
3. The pattern file?.log matches which name?
- A.
file.log - B.
file12.log - C.
fileA.log - D.
file/.log
Answer
C. ? is exactly one character, so fileA.log fits. file.log has zero chars in that spot; file12.log has two.
4. Why use ${NAME}_old instead of $NAME_old?
- A. It is faster
- B. It clearly ends the variable name before
_old - C. Braces enable globs
- D. It disables tilde expansion
Answer
B. ${NAME} delimits the variable name so _old is literal text.
5. What does ~ expand to?
- A. The current directory
- B.
/tmp - C. Your home directory
- D. The root filesystem
Answer
C. Tilde expands to the current user’s home directory.
Match the columns#
| Syntax | Role | ||
|---|---|---|---|
| 1 | {dev,prod} |
A | Match existing filenames |
| 2 | ~ |
B | Generate strings |
| 3 | $VAR |
C | Home directory |
| 4 | $(cmd) |
D | Variable value |
| 5 | *.txt |
E | Insert command output |
| 6 | which bash |
F | Print path of executable |
Answer key
1→B, 2→C, 3→D, 4→E, 5→A, 6→F
Fill in the blank#
- Create three files in one command:
touch report_{2024,2025,2026}.csv - Show today’s date with substitution:
echo $(date +%F) - List one-character suffix logs:
ls log?.txt - Safe default for an unset variable:
echo ${CITY:-Unknown}
Answer key
touch report_{2024,2025,2026}.csv(braces as shown)$(date +%F)or similar date format inside$()log?.txt(question mark)${CITY:-Unknown}(parameter default)
Mini terminal challenge#
Goal: Generate a small project tree with expansions only.
Setup
rm -rf /tmp/linux-lab-pushpjeet/m8-challenge
mkdir -p /tmp/linux-lab-pushpjeet/m8-challenge
cd /tmp/linux-lab-pushpjeet/m8-challenge
Tasks
mkdir -p {src,tests,docs}touch src/main-{dev,prod}.sh tests/test-{1..3}.shecho "Built on $(date +%Y-%m-%d) by $USER" > docs/BUILD.txtls -Rand confirm the names.
Verify: You see six shell files under src/tests and a BUILD.txt containing today’s date.
Stretch: ls $(which touch) and explain what each expansion did.
Next: M9 — Redirection, pipes, and quoting — send output places, chain tools, and quote safely.