find: find and delete files by pattern
Remove all .tmp files from a directory, leaving everything else in place.
A server's disk fills up, and the culprit is temporary files an application
scattered across a dozen directories. Or old logs nobody cleans up.
rm *.tmp won't help here: the asterisk only looks in the current
directory. find walks the whole tree, selects files by name, type, size,
and age, and does whatever you tell it with each one it finds.
Where to practice
Run the examples in an ubuntu:24.04 container. You can start the same
one yourself:
docker run --rm -it ubuntu:24.04 bash
On Linux and in WSL2, find is already installed. macOS has its own
find; the differences are covered in "Common mistakes".
Create a tree to try things on:
mkdir -p /tmp/demo/cache/old /tmp/demo/cache/sessions.tmp /tmp/demo/logs
cd /tmp/demo && touch a.tmp notes.txt cache/b.tmp cache/old/c.tmp cache/sessions.tmp/user1 logs/app.log logs/Error.LOG
Here, sessions.tmp is a directory, not a file. You'll need it below.
How a find command reads
A command has two parts: where to search and what to search for.
find /tmp/demo -name '*.tmp'
/tmp/demo/cache/sessions.tmp
/tmp/demo/cache/old/c.tmp
/tmp/demo/cache/b.tmp
/tmp/demo/a.tmp
/tmp/demo is the starting directory. find walks into it and into every
nested directory at any depth. -name '*.tmp' is a condition: the name
ends in .tmp. Lines print in whatever order find walks the disk; your
order may differ.
Without conditions, find prints the whole tree: find /tmp/demo. That's
handy for seeing what you're working with.
Conditions
-name compares only the file name, without the path, and is case
sensitive. -iname ignores case:
find /tmp/demo -iname '*.log'
/tmp/demo/logs/Error.LOG
/tmp/demo/logs/app.log
The first example caught the sessions.tmp directory: the pattern doesn't
care whether it's a file or a directory. The -type f condition keeps only
regular files, -type d only directories:
find /tmp/demo -name '*.tmp' -type f
/tmp/demo/cache/old/c.tmp
/tmp/demo/cache/b.tmp
/tmp/demo/a.tmp
Several conditions in a row act as "and": a file must match all of them.
-maxdepth 1 limits the depth: only the starting directory itself, nothing
nested.
find /tmp/demo -maxdepth 1 -name '*.tmp'
/tmp/demo/a.tmp
Size and age
-size +100M finds files over 100 megabytes, -size +500k over 500
kilobytes. Plus means "more than", minus means "less than".
-mtime looks at how many full days ago a file was last modified.
-mtime +30 finds files untouched for more than 30 days, -mtime -1
finds files changed in the last day. Days are counted as whole units, and
the fraction is dropped. So a file that's 7 and a half days old doesn't
match -mtime +7, but a file that's 8 days old or more does.
Let's check. The touch -d command sets a file's modification time in the
past:
touch -d '40 days ago' /tmp/demo/logs/old.log
touch -d '180 hours ago' /tmp/demo/logs/week.log
touch -d '200 hours ago' /tmp/demo/logs/eight.log
find /tmp/demo -name '*.log' -mtime +7
/tmp/demo/logs/eight.log
/tmp/demo/logs/old.log
week.log was modified 180 hours ago, which is 7.5 days. It didn't make
the cut.
What to do with what you find
By default, find prints paths. To do something else with the files,
there are actions.
-exec command {} \; runs a command for each file found. {} gets
replaced with the path, and \; ends the command. The backslash is there
so the shell doesn't swallow the semicolon.
find /tmp/demo -name '*.tmp' -type f -exec rm {} \;
-exec command {} + collects paths in batches and runs the command less
often: one rm for hundreds of files instead of hundreds of rm runs.
-delete removes matches itself, without a separate rm process. It will
also delete an empty directory; it can't delete a non-empty one and prints
an error instead.
find /tmp/demo -name '*.tmp' -type f -delete
Whatever you delete from the terminal doesn't go to a trash bin. So the
order is always the same: first run the command without -exec rm or
-delete, read the list, and only then add the action at the end of that
same command.
Why not rm *.tmp
The shell expands the asterisk, not rm. It only substitutes names from
the current directory, doesn't go into subdirectories, and skips files
whose name starts with a dot (.cache.tmp). And when there are a lot of
files, the command doesn't even start:
rm /tmp/many/*.tmp
bash: /usr/bin/rm: Argument list too long
That's 200,000 files in one directory: the list of names didn't fit the
command line's length limit. find /tmp/many -name '*.tmp' -delete handles
it fine, because it doesn't pass names through the command line.
Common mistakes
- Pattern without quotes. If the current directory has matching files, the
shell expands
*.tmpbeforefindeven runs. One such file, andfind . -name *.tmpturns intofind . -name a.tmp: it only searches for that one and says nothing about the rest. Two files, andfindfails withpaths must precede expression. Always put the pattern in single quotes. - The pattern catches directories too.
-name '*.tmp' -exec rm {} \;on thesessions.tmpdirectory printsrm: cannot remove ...: Is a directory. If you only want files, add-type f. And don't switchrmforrm -rto make the error go away: you'll wipe out the directory and everything in it. -deletein the wrong spot. Conditions are checked left to right, and-deletefires the moment its turn comes up. Infind /tmp/demo -delete -name '*.tmp', the delete comes first, andfindwipes out all of/tmp/demo. Always put the action at the end.-owithout parentheses.find . -name '*.tmp' -o -name '*.bak' -deleteonly deletes.bak: "and" binds tighter than "or", and-deletelatched onto the second condition. You needfind . \( -name '*.tmp' -o -name '*.bak' \) -delete.find | xargs rmon names with spaces.xargssplits a name likeold report.tmpinto two, andrmtries to deleteoldandreport.tmp. Use-exec, or the pairfind ... -print0 | xargs -0 rm.-size -1Min GNU find. Size rounds up to whole megabytes, so a 6-byte file counts as 1M. That's why-size -1Monly finds empty files. For "under a megabyte", write-size -1024k. macOS doesn't round this way.- Missing path. On Linux,
find -name '*.tmp'searches the current directory, whilefindon macOS repliesillegal option -- n. Always write the path, even if it's just a dot:find . -name '*.tmp'.
What you'll need for the tasks
- Search by pattern across the whole tree:
find <directory> -name '<pattern>', pattern in quotes. - Filter out directories with
-type f. - Select files by age with
-mtime +N. - Check the list first, then delete with
-deleteor-exec rm {} \;.
Practice in your browser
Linux runs right here. Solve the task and we'll save your progress automatically.
Clean up *.tmp
/tmp/cleanup holds .log and .tmp files. Delete all the .tmp files, the .log files must stay untouched.
0%
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson terminal-text-04-find-by-pattern
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress