Skip to content

find Command Cheat Sheet: Search by Name, Size, Time and Type

A dense GNU find reference: name and path patterns, -size units and rounding, -mtime math, permission tests, pruning, -exec vs xargs, -printf and safe -delete.

●By 10 min read

find is the command every admin types daily and almost nobody reads the manual for. The result is a handful of memorised incantations and a lot of surprises: -size -1M that only returns empty files, -mtime +7 that skips files exactly a week old, and a -prune that prints the very directory you meant to exclude.

This is a reference grouped by what you are filtering on. Everything here is GNU findutils, which is what Debian, Ubuntu, RHEL and Fedora ship; BSD and macOS differences are noted at the end. For a wider set of everyday commands see the Linux tools cheat sheet, and for turning plain-English requests into find, awk and sed pipelines see AI-assisted one-liners.

How a find Expression Is Read

The shape is find [starting-points] [expression]. For every file under each starting point, find evaluates the expression left to right. Adjacent tests are joined by an implicit -a (AND) and evaluation short-circuits, so a failed test stops the rest. If the expression contains no action, -print is added for you.

$ find /etc -name '*.conf' # one start point, one test

$ find /var/log /tmp -type f -name '*.gz' # two start points, implicit AND

$ find . -name '*.log' -o -name '*.txt' # OR

$ find . \( -name '*.log' -o -name '*.txt' \) -mtime -1 # group, then AND

$ find . ! -name '*.bak' # NOT (same as -not)

$ find / -name passwd 2>/dev/null # hide "Permission denied" noise

Quote every pattern β€” an unquoted *.conf is expanded by the shell before find sees it whenever a matching file exists in the current directory. The result is a confusing "paths must precede expression" error or a silently wrong search.

AND binds tighter than OR β€” find . -name a -o -name b -print prints only the b matches, because it parses as -name a -o ( -name b -a -print ). Group with parentheses and put the action outside the group.

Escape the parentheses β€” the shell treats bare parentheses as a subshell. Write \( and \) or quote them as '(' and ')', with spaces on both sides.

Matching by Name and Path

TestMatches againstExample
-name patBasename only, shell glob, case-sensitivefind /etc -name '*.conf'
-iname patBasename, case-insensitivefind ~ -iname '*.jpg'
-path patWhole path; * also matches /find . -path '*/cache/*'
-ipath patWhole path, case-insensitivefind / -ipath '*/readme*'
-regex patWhole path, Emacs regex by defaultfind . -regex '.*\.\(jpg\|png\)'
-regextype posix-extendedSwitches later -regex tests to EREfind . -regextype posix-extended -regex '.*\.(jpg|png)'
-lname patTarget of a symlinkfind /usr/bin -lname '*python3*'
-samefile fEvery hard link to the same inode as ffind / -xdev -samefile /usr/bin/perl
-inum nInode number nfind . -inum 1835023

-name never sees slashes β€” it matches the last path component only, so -name 'conf.d/*.conf' can never match; GNU find warns about it. Use -path '*/conf.d/*.conf'.

-regex is anchored to the full path β€” the pattern must match all of ./src/app/logo.png, not just part of it, which is why regex patterns almost always start with .*.

Filtering by Type and Size

TestMeaning
-type fRegular file
-type dDirectory
-type lSymbolic link (the link itself, not its target)
-type s / pSocket / named pipe (FIFO)
-type b / cBlock device / character device
-xtype lBroken symlinks (the target type is checked, and a dangling link stays l)
-emptyZero-byte regular files and directories with no entries
-size +100MLarger than 100 MiB
-size -10kSmaller than 10 KiB, after rounding up (see below)
-size 0Exactly zero 512-byte blocks, i.e. empty

The size suffixes are c bytes, k KiB, M MiB, G GiB, w two-byte words and b 512-byte blocks, which is also the default when there is no suffix. The trap is rounding: find rounds each file size up to whole units before comparing. A 300 KB file counts as 1M, so -size -1M ("less than one") only matches files of size zero. For anything below one unit, use a smaller unit or bytes.

$ find / -xdev -type f -size +500M -exec ls -lh {} + 2>/dev/null

$ find /var/log -type f -size +100M -size -1G # between 100 MiB and 1 GiB

$ find . -type f -size -1048576c # really under 1 MiB

$ find . -type f -empty # zero-byte files

$ find . -type d -empty -delete # prune empty directories

$ find /etc /usr/local -xtype l # dangling symlinks

$ find /srv -type f -size +1G -printf '%s\t%p\n' | sort -rn | head

5368709120 /srv/backups/db-2026-09-28.dump

2147483648 /srv/vm/disk1.img

Filtering by Time

TestUnitTimestamp
-mtime n24-hour periodsContent last modified
-mmin nMinutesContent last modified
-atime n / -amin nDays / minutesLast read (unreliable on relatime or noatime mounts)
-ctime n / -cmin nDays / minutesInode change: content, owner, mode, link count. Not creation time
-newer fReference fileModified more recently than f
-newermt dateDate stringModified after the given date or time
-daystartOptionMeasure later time tests from today's midnight, not from now

For the day-based tests, find divides the file's age by 24 hours and throws away the fraction before comparing. That makes the three forms behave like this:

ExpressionMatches files whose age isCommon reading
-mtime 0Under 24 hoursModified in the last day
-mtime -7Under 7 days (truncated age 0 to 6)Modified this week
-mtime 7At least 7 but under 8 daysExactly a week old
-mtime +7At least 8 days (truncated age above 7)Older than a week, and one day later than most people expect

$ find /var/log -type f -name '*.gz' -mtime +30 # rotated logs older than ~a month

$ find /etc -type f -mmin -60 # config changed in the last hour

$ find . -daystart -mtime 0 # modified since midnight

$ find /data -newermt '2026-09-01' ! -newermt '2026-09-08' # a date range

$ touch -d '2 hours ago' /tmp/ref && find /srv -newer /tmp/ref

$ find /home -type f -mmin -30 -printf '%TY-%Tm-%Td %TH:%TM %p\n'

-daystart is positional β€” it only affects tests that come after it on the command line, so find . -mtime 0 -daystart does not do what you want.

There is no creation time test in classic find β€” ext4 and XFS record a birth time, and recent coreutils shows it as Birth: in stat. For finding things, -newermt on mtime is what works everywhere.

sponsored

Owners, Permissions and Links

TestMatchesExample
-user u / -uid nOwned by user name or UIDfind /home -user alice
-group g / -gid nOwned by groupfind /srv -group www-data
-nouser / -nogroupUID or GID with no passwd/group entryfind / -xdev -nouser
-perm 644Mode is exactly 644find . -type f -perm 644
-perm -644All of these bits set (others may be too)find . -perm -u+x
-perm /022Any of these bits setfind . -perm /go+w
-readable / -writable / -executableAccessible to the user running find (uses access(2))find /etc -writable
-links nHard link countfind . -type f -links +1

$ find / -xdev -type f -perm -4000 # SUID binaries

$ find / -xdev -type f -perm /6000 # SUID or SGID

$ find /var/www -type f -perm /o+w # world-writable files

$ find / -xdev \( -nouser -o -nogroup \) -ls # left behind after userdel

$ find /srv/share -type d ! -perm -g+x # dirs the group cannot enter

$ find /home -user alice -type f -size +1G # one user's big files

Minus means all, slash means any β€” -perm -6000 needs both SUID and SGID; -perm /6000 needs either. The old -perm +6000 form for "any" is no longer accepted by GNU find.

Symbolic modes work β€” -perm -g+w and -perm /u=x,g=x are valid and often clearer than octal. The chmod calculator converts between the two, and our permissions guide explains what the special bits do.

Controlling the Walk

$ find /etc -maxdepth 1 -type f -name '*.conf' # top level only

$ find . -mindepth 2 -maxdepth 3 -type d # skip the first level

$ find / -xdev -type f -size +1G # stay on this filesystem

$ find . -path ./node_modules -prune -o -type f -name '*.js' -print

$ find . \( -name .git -o -name node_modules \) -prune -o -type f -print

$ find -L /opt/app -type f -name '*.jar' # follow symlinks

$ find / -name sshd_config -print -quit 2>/dev/null # stop at the first match

-prune needs an explicit -print β€” without it, the implicit -print wraps the whole expression and the pruned directory names are printed too. The idiom is always ... -prune -o ... -print.

! -path is not pruning β€” find . ! -path './node_modules/*' hides results but still walks every file below node_modules. On a large tree -prune is dramatically faster.

-maxdepth goes first β€” it is a global option. GNU find warns if you put it after a test, because it applies to the whole search regardless of position.

-xdev on whole-system searches β€” it keeps find out of /proc, /sys, network mounts and other filesystems. -mount is an older synonym.

Acting on Results: -exec, xargs, -delete, -printf

ActionWhat it does
-print / -print0Print the path, newline- or NUL-terminated
-lsls -dils style line for each match
-exec cmd {} \;Run cmd once per file
-exec cmd {} +Run cmd with as many files per call as fit (like xargs)
-execdir cmd {} +Same, but run from the file's own directory
-ok cmd {} \;Ask for confirmation before each run
-deleteRemove the file or empty directory; implies -depth
-printf fmtCustom output with format directives
-quitExit immediately, usually after the first match

$ find . -name '*.tmp' -exec rm -f {} + # one rm per batch of files

$ find . -name '*.sh' -exec chmod 755 {} \; # one chmod per file

$ find . -type f -name '*.log' -print0 | xargs -0 -P4 gzip # parallel, safe for any name

$ find /var/log/app -name '*.log' -mtime +14 -print # dry run first...

$ find /var/log/app -name '*.log' -mtime +14 -delete # ...then swap -print for -delete

$ find /etc -type f -printf '%TY-%Tm-%Td %u %m %s %p\n' | sort | tail -3

2026-09-28 root 644 1203 /etc/ssh/sshd_config

2026-09-30 root 640 4512 /etc/sudoers.d/ops

2026-10-01 root 644 287 /etc/hosts

-printf directiveOutput
%p / %f / %hFull path / basename / parent directory
%s / %kSize in bytes / disk usage in 1 KiB blocks
%m / %MOctal mode / ls-style mode (-rw-r--r--)
%u / %gOwner / group name
%i / %nInode number / hard link count
%lSymlink target (empty for other types)
%T@ / %TY-%Tm-%Tdmtime as epoch seconds / as a date (A and C prefixes give atime and ctime)

Never put -delete first β€” the expression is evaluated in order, so find . -delete -name '*.tmp' deletes everything and then tests the name. Write the search, run it with -print, then append -delete at the end.

-delete and -prune do not mix β€” -delete turns on -depth, and -prune has no effect under -depth. Use -exec rm if you also need to skip directories.

Prefer -print0 with xargs β€” plain find | xargs breaks on spaces and newlines in filenames. -print0 | xargs -0, or -exec ... +, handles every legal name.

BSD and macOS find β€” there is no -printf or -regextype; use -E for extended regex and pipe to stat -f for custom output. Installing GNU findutils through a package manager provides gfind.

Name-only searches across the disk β€” locate (plocate or mlocate depending on distro) queries an index rebuilt by updatedb, usually daily. It is near-instant but misses files created since the last run.

Frequently Asked Questions

What is the difference between -exec {} \; and -exec {} + in find?

With \; find runs the command once for every matching file, which is slow for thousands of files. With + it appends as many paths as fit on one command line and runs the command a few times, the same way xargs does. Use + whenever the command accepts multiple file arguments.

Why does find -size -1M only return empty files?

find rounds each file size up to whole units before comparing. Any non-empty file smaller than a megabyte rounds up to 1M, which is not less than 1, so only zero-byte files match. Use a smaller unit such as -size -1024k or exact bytes such as -size -1048576c.

How do I exclude a directory from a find search?

Use -prune: find . -path ./node_modules -prune -o -type f -print. The explicit -print at the end matters, otherwise the pruned directory itself is printed. Filtering with ! -path hides the results but still walks the whole directory, which is much slower on large trees.

Does find -mtime +7 mean older than 7 days?

Not exactly. find truncates each file's age to whole days, and +7 means the truncated value is greater than 7, so a file must be at least 8 days old to match. If you need precise hours, use -mmin +10080 for 7 days in minutes.

How do I safely delete files with find?

Build the expression with -print first and check the list. When it is right, replace -print with -delete at the end of the same command. Never place -delete before the tests, because find evaluates left to right and would delete every file it visits. Add -xdev when starting from a high-level directory.

sponsored

Related Tools

See also