Course outline · 0% complete

0/29 lessons0%

Course overview →

Wildcards and find

lesson 4-2 · ~9 min · 11/29

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:

PatternMatchesExample
*Any run of characters, including none*.txt matches every name ending in .txt
?Exactly one characterphoto?.png matches photo1.png but not photo10.png
[ab]One character from the setreport[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.

*.txtthe shell expands this pattern…notes.txtdraft.txtphoto.pngscript.sh✓ match✓ match✗ skipped✗ skipped…so ls *.txt actually runs asls draft.txt notes.txt
The shell, not the command, resolves wildcards. The command receives the already-expanded list of names.

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 d finds directories only, -type f files 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 | sort

Output

./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" | sort

Output

./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.