← Linux from ScratchCheat sheet
M8Linux from Scratch

M8 — Bash expansions and globs

Time: about 45–75 minutes
Lab root: /tmp/linux-lab-pushpjeet

Prerequisites#

  • Modules M3–M5 (paths, listing, creating files)
  • A bash shell (echo $SHELL should mention bash)

Learning objectives#

By the end of this module you will be able to:

  1. Generate lists of names with brace expansion
  2. Expand your home directory with tilde expansion
  3. Use parameter expansion for variables ($VAR, ${VAR})
  4. Capture command output with command substitution ($(…))
  5. Match filenames with pathname expansion (globs: * ? [])
  6. Locate a binary with which and 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#

  1. Expecting braces to filter files — {a,b} always generates both strings; use globs to match what exists.
  2. Quoting too early — echo "*.txt" prints the star; quotes suppress globbing (more in M9).
  3. Spaces around = in assignment — LAB = value is wrong; use LAB=value.
  4. Confusing $(…) with (…) — parentheses alone start a subshell; dollar-parentheses capture output.
  5. 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
  1. Create host-{web,db,cache}.cfg with one touch using braces. Confirm with ls.
  2. Print your home directory with echo ~ and with echo $HOME.
  3. Set COURSE=linux-from-scratch and echo Course=${COURSE}.
  4. Print Binary: $(which mkdir).
  5. Create a1.txt a2.txt b1.txt and list only a?.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#

  1. Create three files in one command: touch report_{2024,2025,2026}.csv
  2. Show today’s date with substitution: echo $(date +%F)
  3. List one-character suffix logs: ls log?.txt
  4. Safe default for an unset variable: echo ${CITY:-Unknown}
Answer key
  1. touch report_{2024,2025,2026}.csv (braces as shown)
  2. $(date +%F) or similar date format inside $()
  3. log?.txt (question mark)
  4. ${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

  1. mkdir -p {src,tests,docs}
  2. touch src/main-{dev,prod}.sh tests/test-{1..3}.sh
  3. echo "Built on $(date +%Y-%m-%d) by $USER" > docs/BUILD.txt
  4. ls -R and 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.

Continue

← Linux from Scratch hub · Cheat sheet · All tutorials