Sign in
← Terminal: text and search
4 min · medium · Lesson 4 of 5

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

What you'll need for the tasks

Practice in your browser

Linux runs right here. Solve the task and we'll save your progress automatically.

01

Clean up *.tmp

/tmp/cleanup holds .log and .tmp files. Delete all the .tmp files, the .log files must stay untouched.

{ i }

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson terminal-text-04-find-by-pattern