Globbing exercises


Predict the output before you run each command, then check it against the answer key at the end.
Setup
Run this in a VM for testing purposes:
mkdir ~/glob-lab && cd ~/glob-lab
touch report{1..12}.txt report{a..e}.log
touch .hidden1 .hidden2 .config
touch file{A..C}{1..3}.conf
touch Data.csv data.csv DATA.CSV
touch img_2024-0{1..3}.png img_2025-0{1..3}.jpg
touch 'my file.txt'
mkdir -p dir1/sub dir2 .hiddendir
touch dir1/a.txt dir1/sub/b.txt dir2/c.log
Check that it worked with ls -A.
Part 1: ?, [ ], and negation
ls report?.txt
ls report??.txt
ls report[1-5].txt
ls report[!1-5].txt
ls file[AB]?.conf
ls file[^A]?.conf. Is ^ the same as ! here?
ls [[:upper:]]*
ls [[:digit:]]*. What happens when nothing matches?
Part 2: Hidden files and directories
ls versus ls -d . Why do they differ?
ls -d .*. Look closely at the first two entries.
ls -d .[!.]*
ls -d / and ls -d ./
Part 3: Braces (not globbing, but often mixed with it)
echo report{1,2,3}.txt
ls img_2024-0{1..3}.png
ls *.{png,jpg}
echo report{1,99}.txt versus ls report{1,99}.txt. What does this show about braces versus globs?
ls report{1[0-2],5}.txt
Part 4: Quoting and the shell's role
ls my file.txt, ls 'my file.txt', ls my\ file.txt, ls my?file.txt
ls "*.log"
echo .log and ls .log. Who expands the *?
set -f; ls *.log; set +f
Part 5: Directories and depth
ls -d dir*/*
ls //*.txt
ls -R and ls -aR. Which one shows .hiddendir?
Part 6: Write your own pattern
Write one command for each, using only ls, globs, and braces:
A. Only report3.txt through report11.txt.
B. Files whose extension is exactly 3 characters and doesn't start with t.
C. All .png and .jpg files from 2025 only.
D. Everything in dir1 and dir2, but not their subdirectories' contents, with one command and no -R.
E. Data.csv and data.csv, but not DATA.CSV.
Bonus: locale behavior
Run LC_ALL=C ls -d [a-d]* and then ls -d [a-d]*. Ranges like [a-d] can depend on the locale and shell version, so results may differ between systems. [[:lower:]] is the portable choice.
Answer key
report1.txt to report9.txt. ? matches exactly one character.
report10.txt, report11.txt, report12.txt.
report1.txt to report5.txt.
report6.txt to report9.txt. report10 and above have two characters, so they don't match.
The six fileA? and fileB? files.
Yes. Bash accepts both ! and ^, but ! is the POSIX form, so prefer it. The result is the six fileB? and fileC? files.
Data.csv and DATA.CSV.
Nothing starts with a digit, so bash passes the pattern through literally and ls errors with "No such file or directory".
Without -d, ls receives dir1 dir2 among its arguments and lists their contents. With -d, it lists the directories themselves.
Depending on the bash version, . and .. may be included. The rest are .config .hidden1 .hidden2 .hiddendir.
.config .hidden1 .hidden2 .hiddendir. The [!.] excludes . and ...
dir1/ dir2/ for the first. .hiddendir/ for the second.
report1.txt report2.txt report3.txt
img_2024-01.png, img_2024-02.png, img_2024-03.png.
All six img_* files. Bash expands the braces first (*.png *.jpg), then globs each one.
echo prints both names. ls lists report1.txt and errors on report99.txt. Braces generate strings whether or not files exist, while globs only match existing files.
report10.txt, report11.txt, report12.txt, report5.txt. Braces expand first, then the [0-2] glob matches.
The unquoted version fails because ls receives two arguments, my and file.txt. The quoted and escaped versions work. my?file.txt also works, because ? matches the space.
It fails, because the quotes stop expansion and no file is literally named *.log.
The shell expands *, not the command. echo and ls both receive the already-expanded list.
set -f disables globbing, so ls gets the literal *.log and errors. set +f turns it back on.
dir1/a.txt, dir1/sub, dir2/c.log.
dir1/sub/b.txt
ls -aR. Plain -R skips hidden entries.
Part 6
A. ls report{[3-9],10,11}.txt, or ls report[3-9].txt report1[01].txt
B. ls -d *.[!t]??. This also matches DATA.CSV and the .log, .png, and .jpg files.
C. ls 2025.{png,jpg} or ls img_2025-*
D. ls -d dir[12]/*. It lists dir1/a.txt, dir1/sub, and dir2/c.log. The sub entry appears as a name only, because of -d.
E. ls [Dd]ata.csv
Extended globbing (extglob, e.g. !(*.txt)) and globstar (**) are bash options beyond what LPIC-1 expects, so I left them out.
Whenever you're unsure what a glob will do, put echo in front of the command and you'll see exactly what the command would receive. It's also a safe habit before running something destructive like rm *.log. So first, it's the shell expansion (before even the ls command runs) and then is when the command runs.
A one-line memory hook: -d means "don't look inside". It only affects directories and leaves files alone. The filtering happens in the shell's glob, before ls runs, and -d only decides what ls does with the directories it receives.
Contact
hello@unixtips.eu
© 2025. All rights reserved.