Wildcards & your first pipes
Typing twelve file names is boring. Typing *.jpg isn't. Wildcards let one short pattern stand for many files, and brace expansion writes long lists for you. Then the pipe | joins small commands into bigger ones. Together they turn ten minutes of clicking into one line.
You will learn
- The wildcards
*,?and[ ], and why they skip hidden files - Brace expansion:
{a,b,c}and{1..10} - Previewing with
echobefore a riskyrm - Quotes, and when to stop the shell from expanding things
- Your first pipes:
| wc -l,| grep,| sort,| head
The shell expands them, not the command
When you type ls *.jpg, ls never sees the star. bash looks at the files first, replaces *.jpg with the matching names, and then runs ls IMG_0001.jpg IMG_0002.jpg …. That's why the same patterns work with every command: cp, mv, rm, tar, anything.
| Pattern | Matches | Example |
|---|---|---|
* | any number of any characters (even none) | *.log, IMG_*, *2026* |
? | exactly one character | IMG_000?.jpg (0001 to 0009) |
[abc] | one character from the list | [abc]* (names starting with a, b or c) |
[0-9] | one character from a range | app-2026-09-0[1-5].log |
[!0-9] | one character NOT in the list | [!.]* |
- Linux is case-sensitive:
*.jpgdoesn't matchPHOTO.JPG. Use*.[jJ][pP][gG], or both patterns. *skips hidden files (names starting with a dot). That's on purpose, sorm *doesn't delete your.bashrc.ls -ashows them.- If nothing matches, bash passes the pattern unchanged, so
ls *.xyzcomplains about a file literally called*.xyz.
Brace expansion: lists without typing
mkdir -p project/{src,docs,tests} # three folders
touch project/src/file{1..3}.py # file1.py file2.py file3.py
echo {a..e} # a b c d e
cp config.yml{,.bak} # cp config.yml config.yml.bak
Braces are different from wildcards: they make names whether or not the files exist. Wildcards only match files that are already there.
Look before you leap: echo first
echo rm logs/app-2026-09-0[1-3].log # shows exactly what would be deleted rm logs/app-2026-09-0[1-3].log # same line without "echo"
rm -rf /tmp/old * (with a space before the star) deletes everything in your current folder, and so does rm *.txt with a typo like rm * .txt. Put echo in front of any rm with a wildcard, read what it prints, then remove the echo.
Quotes switch expansion off
echo *.txt # the names of your .txt files echo '*.txt' # literally: *.txt find . -name '*.png' # quotes: find does the matching, in every subfolder ls "screenshot 1.png" # quotes keep a name with a space as ONE argument
With find -name you want the pattern to reach find, so quote it. Otherwise bash expands it in the current folder first, and find gets the wrong question.
Your first pipes
A pipe sends one command's output into the next command's input. Small tools, snapped together:
ls photos | wc -l # how many files? ls logs | grep error # only names containing "error" ls logs | sort -r | head -3 # the three newest dated logs ls -l photos/*.jpg | sort -k5 -n | tail -1 # the biggest jpg (column 5 = size)
The Linux Sysadmin path goes much further (redirection, awk, cut, uniq…), but these four will carry you a long way: Linux Sysadmin, lesson 3.
Practice: sort out the photos 📸
You have a photos folder (and one sneaky hidden file) and a logs folder with a log per day. Find things with wildcards, build a project folder with braces, delete old logs safely, and count things with pipes.
Quick check
1. Which files does report-0?.txt match?
✓ ? is exactly one character.
2. Why doesn't ls * show .bashrc?
✓ Use ls -a to see hidden files.
3. What's the safe habit before rm logs/*2025*?
✓ echo shows you exactly what the shell will hand to rm.