You need to find a file, but you only remember part of its name, or you need to find every file larger than 100 MB, or every config file changed in the last week. The find command searches by file metadata (name, size, type, time, permissions) rather than contents, which makes it the right tool when you know something about what you’re looking for but not where it lives. This guide walks through how to build find expressions, what the results mean, and which mistakes can turn a useful command into a destructive one.

Most administrators reach for find when they cannot remember where they saved a file. That is the simplest use, but it is not the most useful. find becomes powerful when you combine criteria, for example, “show me every regular file under /var/log larger than 100 MB that was modified in the last seven days” or “find every file owned by www-data with world-write permission.”

The critical thing to understand before running anything: find evaluates its expression against every file it encounters, from left to right, stopping as soon as the outcome is known. That means order matters, and it means a misplaced -delete can remove more than you intended. I would always build and test a find expression in two steps: first run it with -print (or no action at all, since printing is the default) to review the match list, then add the destructive action only after I am confident the matches are correct.

The basic structure

find takes three parts: where to start, what to test for, and what to do with matches. If you omit the starting point, find searches the current directory (.). If you omit the expression, it prints every path it finds, which is rarely what you want, because you will get every file and directory under that tree.

find [starting-point...] [expression]

I typically start with a narrow starting point rather than searching from /. Searching the whole filesystem means find must stat every file it encounters, including mounted network shares and removable media, which makes the command slow and can surface results you cannot act on. It is faster and safer to start where you actually expect the file to live.

# Print every file and directory under the current directory
find .

# Print every file and directory under /etc
find /etc

Those commands print everything, which is rarely what you want. You add tests to narrow the results. The key insight is that find does not search file contents. It searches file metadata. If you need to search inside file contents, combine find with grep or use grep -r directly.

Search by name

-name matches the base name of the file (the final component of the path), not the full path. Use quotes so your shell does not expand the wildcard before find sees it. An unquoted *.log is expanded by your shell into a list of matching filenames in the current directory, so find never receives the pattern at all.

# Exact filename
find /home -name "budget.csv"

# Case-insensitive match
find /home -iname "BUDGET.CSV"

# Wildcard match (quoted!)
find /var/log -name "*.log"

# Match part of the full path (not just the filename)
find /var/www -path "*/cache/*"

-name is case-sensitive. -iname ignores case. Both match the filename only; if you need to match part of the directory path, use -path. The distinction matters because -path tests the entire path string, so find /var/www -path "*/cache/*" finds anything under any cache directory, while -name "cache" would only match a file or directory literally named cache at the top level.

Search by type

Linux treats directories, symbolic links, sockets, and regular files as different types. -type filters by that distinction. I use -type f most often because I usually want actual files, not directories or symlinks getting in the way.

TypeMeaning
fRegular file
dDirectory
lSymbolic link
The three types you will use most often.
# Regular files only
find /etc -type f -name "*.conf"

# Directories only
find /var -type d -name "backup"

# Symbolic links only
find / -type l -name "*.so"

Combining -type f with a name pattern is how you find files of a certain kind without directories getting in the way. Without -type f, a search for *.conf under /etc might return directories whose names end in .conf, which you probably do not want.

Search by size

-size compares the file’s disk usage against a threshold. The number is interpreted in 512-byte blocks unless you add a suffix. This is one of the easiest places to misread results: -size 100 means exactly 100 blocks (50 KB), not 100 bytes or 100 KB.

SuffixUnitExampleMeaning
(none)512-byte blocks-size 100Exactly 100 blocks (50 KB)
cBytes-size 1024cExactly 1,024 bytes
kKilobytes-size +1000kLarger than 1,000 KB
MMegabytes-size +100MLarger than 100 MB
GGigabytes-size -5GSmaller than 5 GB
# Files larger than 100 MB
find /var -type f -size +100M

# Files between 1 and 2 MB
find /var -type f -size +1M -size -2M

# Empty files (exactly 0 bytes)
find /tmp -type f -size 0

A plus sign means “larger than,” a minus sign means “smaller than,” and no sign means exactly that size. Size searches are the fastest way to find large files eating up disk space, which is why find pairs naturally with disk-full troubleshooting. If your disk is full and you do not know why, start here.

Search by modification time

-mtime counts in 24-hour periods, not calendar days. This distinction trips people up. -mtime -7 means modified within the last 7 days (less than 168 hours ago), not “last week.” -mtime +30 means modified more than 30 days ago. -mtime 1 means modified between 24 and 48 hours ago, exactly one 24-hour period back.

ExpressionMeaning
-mtime -7Modified within the last 7 days (less than 168 hours ago)
-mtime +30Modified more than 30 days ago
-mtime 1Modified exactly 1 day ago (between 24 and 48 hours ago)
-mmin -60Modified within the last 60 minutes
# Configuration files changed in the last week
find /etc -type f -name "*.conf" -mtime -7

# Log files older than 90 days (candidates for archiving or deletion)
find /var/log -type f -name "*.log" -mtime +90

-atime checks when the file was last read; -ctime checks when the file’s status (permissions, ownership, contents) last changed. Use -mtime when you care about content edits. If you are trying to find recently modified configuration files after an incident, -mtime -7 narrows to the relevant window without you doing timestamp arithmetic by hand.

Search by permissions

-perm matches files by their permission bits. There are three forms, and confusing them is a common source of wrong results.

FormMeaningExampleMatches
-perm modeExact permission match-perm 644Files with exactly mode 644
-perm -modeAll specified bits are set-perm -444Files readable by everyone (at least 444)
-perm /modeAny of the specified bits are set-perm /222Files writable by anyone (owner, group, or other)
# Files with exactly mode 644
find /var/www -type f -perm 644

# Files writable by anyone (security audit)
find / -type f -perm /222

# Files readable by everyone
find /etc -type f -perm -444

# Setuid binaries (potential privilege escalation targets)
find /usr/bin -type f -perm /4000

Permission searches are useful for security audits, but they can also be slow if you search from / because find must stat every file it encounters. For a security audit, I would scope the search to the directories that matter: /etc, /var/www, /usr/bin — rather than running from / and waiting for find to traverse every mounted filesystem.

Excluding directories

Searching from / means traversing mounted filesystems, network shares, and directories full of noise you do not care about. -prune tells find not to descend into a directory at all.

# Skip /media (removable mounts) while searching for large files
find / -path /media -prune -o -size +200000 -print

# Skip .git directories while searching a project
find . -path "*/.git/*" -prune -o -name "*.py" -print

-prune returns true for the pruned directory, so you combine it with -o (OR): “if this path matches, prune it; otherwise, apply the rest of the expression.” I prefer -prune over -xdev when I know exactly which directories to skip, because -xdev stays on one filesystem and can skip directories you actually want to search. Use -xdev when you want to avoid crossing mount points entirely; use -prune when you want to skip specific noisy directories.

Acting on what you find

Finding files is only half the job. The other half is doing something with them. -exec runs a command on each match. -delete removes matches. -print outputs the path (the default if you specify no action).

# Delete old log files (DANGEROUS - test first without -delete)
find /var/log -type f -name "*.log" -mtime +90 -delete

# Change permissions on matching config files
find /etc -type f -name "*.conf" -exec chmod 644 {} +

# Search inside matching files for a string
find /etc -type f -name "*.conf" -exec grep -l "listen" {} +

-exec command {} + runs the command once with all matches appended, which is faster than -exec command {} \; (which runs the command once per file). Use + when the command accepts multiple arguments.

Always test before you destroy. I would never run -delete or -exec rm on the first try. Run the same find without the destructive action first, review the match list, and only then add -delete. The files find returns are the files that will be deleted. If the list looks wrong, the command is wrong, and you should fix the expression before making it destructive.

The -execdir alternative

-exec runs the command from the starting directory, which means relative paths in your command resolve against where you ran find, not where the file lives. -execdir runs the command from the subdirectory containing the matched file, which avoids a class of race-condition security problems when acting on files in shared directories like /tmp.

# Safer when acting on files you do not fully trust
find /tmp -type f -name "*.tmp" -execdir rm {} +

Use -execdir when you are acting on files in shared or writable directories. The risk with -exec is subtle: if a file is moved or replaced between the time find locates it and the time your command acts on it, you might be operating on a different file than you expected. -execdir reduces that window by acting from the file’s own directory.

Common mistakes

  • Unquoted wildcards. find . -name *.log lets your shell expand *.log before find sees it, so find receives a list of literal filenames instead of a pattern. Always quote: find . -name "*.log".
  • -delete implies -depth. -delete processes directory contents before the directory itself. You cannot usefully combine -prune and -delete.
  • Running destructive commands without testing first. Always run the same find without -delete or -exec rm to review the match list before making it destructive.
  • Searching from / without excluding mount points. You may traverse network filesystems or removable media unintentionally. Use -prune or -xdev (stay on one filesystem).

Debugging a find expression

When find returns nothing, or too much, the expression is usually evaluating differently than you expect. -D search shows how find parses your expression, and -D stat shows each file it examines. These debugging flags are useful when a complex expression is not returning the results you expect, they let you see whether find is parsing your expression the way you think it is.

# Debug how find parses your expression
find -D search /etc -name "*.conf" -mtime -7

# Debug which files find examines
find -D stat /etc -name "*.conf" 2>/dev/null | head -20

If you are getting permission errors on directories you cannot read, redirect stderr to /dev/null or run with sudo. I would not run find / with sudo as a first resort, it is slower and more invasive than necessary. Start with a specific directory, and escalate privileges only if permission errors are actually blocking you.

# Silence "Permission denied" errors
find / -name "budget.csv" 2>/dev/null

The questions worth asking

  • Are you searching by name, type, size, time, or permissions? Combine them as needed.
  • Did you quote your wildcards so the shell does not expand them?
  • Did you review the match list before adding -delete or -exec rm?
  • Do you need to exclude directories like .git or /media?
  • Is -mtime behaving as you expect? Remember it counts 24-hour periods.

References

Related Tools

Try these companion tools on DevStackIO Tools: