Wildcards: patterns for file NAMES
The payoff of this lesson is acting on groups of files in one command, such as backing up every .png or deleting every .tmp. With find, it also becomes possible to locate a file you know exists somewhere in a big project without clicking through directories.
grep searches inside files. To match file names, the shell provides wildcards, also called globs:
| Pattern | Matches | Example |
|---|---|---|
* | Any run of characters, including none | *.txt matches every name ending in .txt |
? | Exactly one character | photo?.png matches photo1.png but not photo10.png |
[ab] | One character from the set | report[12].txt matches report1.txt and report2.txt |
The mental model that prevents confusion later: the shell expands the pattern before the command runs. In ls *.txt, ls never sees the star at all, because the shell has already rewritten the line as ls draft.txt notes.txt. That is why wildcards work identically with every command, as in cp *.png backup/, rm *.tmp, or grep todo *.md.
Star versus question mark on the same directory
Five files and two patterns. The * grabs both .txt files regardless of name length, and the ? matches the single digit in photoN.png.
touch notes.txt draft.txt photo1.png photo2.png script.sh ls *.txt ls photo?.png
Output
draft.txt notes.txt photo1.png photo2.png
Neither script.sh nor any .png file appears in the first listing, because *.txt requires the name to end in .txt. The results also come out alphabetically rather than in the order the files were created, since the shell sorts the names it expands.
find: search the whole tree
Wildcards only look in the current directory. find walks a directory and all its subdirectories:
find . -name "*.txt"
find . -type d- The first argument is where to start (
.= here, from lesson 2-1). -name "*.txt"filters by name. The quotes matter: they stop the shell from expanding the star early, so find itself gets the pattern.-type dfinds directories only,-type ffiles only.
find prints results in whatever order it walks the tree, so in the example we pipe into sort for a predictable order (pipes get the full treatment in unit 5).
Finding files buried in subdirectories
find digs guide.txt out of docs/ even though the shell never moves there, then a second call lists every directory in the tree.
mkdir -p src docs
touch src/app.js src/util.js docs/guide.txt readme.txt
find . -name "*.txt" | sort
find . -type d | sortOutput
./docs/guide.txt ./readme.txt . ./docs ./src
Every result is a relative path beginning with ., the directory where the search started, which means the output can be fed straight into another command. The second listing includes . itself, because the starting directory counts as a directory that matched -type d.
The pattern that matches chapter1.md and chapter2.md while excluding chapter10.md is chapter?.md.
? stands for exactly one character, so the name must have precisely one character between chapter and .md. chapter10.md has two, so it fails to match. A pattern built on *, such as chapter*.md, would accept any number of characters there and pull in chapter10.md along with the rest.
Here find filters by extension across a nested tree. The music/ directory holds two .mp3 files in separate subdirectories plus a .png that should not appear in the results, and sort makes the output order predictable.
mkdir -p music/rock music/jazz
touch music/rock/song1.mp3 music/jazz/song2.mp3 music/cover.png
find . -name "*.mp3" | sortOutput
./music/jazz/song2.mp3 ./music/rock/song1.mp3
Two things make this work. find . -name "*.mp3" walks every subdirectory on its own, so neither rock nor jazz has to be named explicitly, and the quotes around "*.mp3" keep the shell from expanding the star before find receives it. music/cover.png is absent because it fails the name filter.