Find files and search text: find and grep recipes
Find files by name, size and age, search inside them, and delete old files without surprises.
Part 7 of 12 in Linux basics for self-hosters. Last updated
In short: find searches file names and properties: find . -type f -name '*.log' -mtime +7 lists log files older than a week, and find . -type f -size +200M lists files over 200 MiB. grep searches content: grep -rn 'error' /etc prints every matching line with its file and line number. GNU find takes plain numbers for -mtime (+7, not +7d). Always run a find with -print before you add -delete.
find: files by name, type, size and age
find walks a directory tree and prints every path that matches all the tests you
give it. The first argument is where to start; . is the current directory. Every
recipe below is for GNU find, the one on every mainstream Linux distribution; the reference is
find(1).
find . -name '*.log' # by name; quote the pattern
find . -iname '*.jpg' # name, ignoring case
find /etc -type d -name 'systemd' # directories only (f = files, l = links)
find . -type f -mtime +7 # changed more than 7 days ago
find . -type f -mtime -1 # changed in the last 24 hours
find . -type f -mmin -30 # changed in the last 30 minutes
find . -type f -newermt '2026-09-01' # changed after a date
find . -type f -size +200M # bigger than 200 MiB
find . -type d -empty # empty directories
find . -type f -user alice # owned by alice
find . -type f -perm -o+w # writable by everyone: worth a look
find / -xdev -type f -size +1G 2>/dev/null # stay on one file system, hide "Permission denied" - Age is counted in whole days with the fraction dropped:
+7means "more than 7 full days", so in practice 8 days or older.-7means less than 7. - Size units are
k,MandG, in steps of 1024.cmeans bytes. Stick to+with sizes: find rounds up to the unit, so-size -1Monly matches empty files. - Quote patterns such as
'*.log'. Without quotes the shell expands the star first, and find gets a list of names from the current directory instead of a pattern.
The examples on this page ran in a small practice tree: two config files, three logs (the first
one dated 40 days back with touch -d) and a 120 MiB file. Build the same tree to
follow along, then try the tests:
$ find . -type f | sort
./etc/app/app.conf
./etc/db.conf
./logs/app-1.log
./logs/app-2.log
./logs/app-3.log
./logs/big.img
$ find . -name '*.conf'
./etc/app/app.conf
./etc/db.conf
$ find logs -type f -mtime +30
logs/app-1.log
$ find . -size +100M -exec ls -lh {} +
-rw-r--r-- 1 user user 120M Sep 28 06:30 ./logs/big.img Real output on openSUSE Tumbleweed, GNU findutils, 2026-09-28; user and host names and addresses redacted.
Doing something with the results
find . -type f -size +200M -exec ls -lh {} + # details of each match
find . -type f -size +5M -printf '%s\t%p\n' | sort -rn | head # biggest first, in bytes
find . -name '*.log' -mtime +30 -print # 1. look
find . -name '*.log' -mtime +30 -delete # 2. delete the same list
find . -name '*.sh' -exec chmod u+x {} + # run a command on all of them -exec ... {} + passes many file names to one command, which is fast.
-exec ... {} \; runs the command once per file, which you only need when the
command accepts a single name. Both handle spaces in names correctly, which a pipe from
ls does not.
grep: searching inside files
grep 'error' app.log # lines containing error
grep -i 'error' app.log # ignore case: Error, ERROR
grep -rn 'PermitRootLogin' /etc/ssh # recursive, with file and line number
grep -rl 'example.com' /etc # only the names of matching files
grep -rw 'disk' . # whole word: not "diskette"
grep -v '^#' sshd_config # lines that do not start with #
grep -c 'Failed password' auth.log # count matching lines
grep -E 'error|warning' app.log # either word (extended regex)
grep -F '1.2.3.4' access.log # fixed string: dots are dots
grep -C 2 'panic' app.log # 2 lines of context around each match
grep -rn --include='*.conf' 'Listen' /etc
grep -rn --exclude-dir=.git 'TODO' .
Without -F, grep treats the pattern as a regular expression, where
. matches any character. The full list of options is in
grep(1). Logs of systemd
services are not in text files; search those with journalctl -g, covered in
the systemctl and journalctl guide.
In the same practice tree:
$ grep -rn 'port' etc/
etc/app/app.conf:1:port = 8080
etc/db.conf:2:port=5432
$ grep -rc ERROR logs/ --include='*.log'
logs/app-1.log:1
logs/app-2.log:1
logs/app-3.log:1
$ grep -v '^#' etc/app/app.conf
port = 8080
listen = 127.0.0.1 Real output on openSUSE Tumbleweed, GNU grep, 2026-09-28; user and host names and addresses redacted.
find and grep together
grep's own -r is enough most of the time. Combine the two when you need find's
tests, such as age or size, to pick the files first:
find /var/log -name '*.log' -mtime -2 -exec grep -l -i 'error' {} +
find . -type f -name '*.txt' -print0 | xargs -0 grep -l 'invoice' $ find . -name '*.conf' -exec grep -n port {} +
./etc/app/app.conf:1:port = 8080
./etc/db.conf:2:port=5432
$ find . -name '*.log' -print0 | xargs -0 grep -l ERROR | sort
./logs/app-1.log
./logs/app-2.log
./logs/app-3.log Real output on openSUSE Tumbleweed, 2026-09-28; user and host names and addresses redacted.
-print0 and xargs -0 separate names with a null byte, so names with
spaces or newlines survive the pipe. Click such commands together with the
find and grep command builder, or paste one
you found online into the shell command explainer
before running it.
Faster alternatives
ripgrep (rg) searches
recursively by default, skips files listed in .gitignore and is much faster on
big trees: rg -n 'error' /srv/app. fd does the same for file names:
fd -e log. Both are packaged for openSUSE, Fedora, Debian and Ubuntu (on Debian
and Ubuntu the fd package is fd-find and the command is fdfind). Learn find and grep anyway:
they are on every server you will ever log in to.
$ rg -n listen
etc/app/app.conf:2:# listen = 0.0.0.0
etc/app/app.conf:3:listen = 127.0.0.1 Real output on openSUSE Tumbleweed, ripgrep, 2026-09-28; user and host names and addresses redacted.
Common mistakes
find: invalid argument `+7d' to `-mtime'
GNU find takes a plain number of days. Write -mtime +7, or -mmin +90
for minutes. Units like 7d belong to other tools, such as
fd --changed-within 7d.
-delete in the wrong place
find evaluates its expression from left to right. find . -delete -name '*.log'
deletes everything first and checks the name afterwards. Put -delete last, and
run the command with -print first.
Piping ls into grep or xargs
ls | xargs rm breaks on the first file name with a space and may remove the wrong
file. Use find with -exec or -print0 | xargs -0, or plain shell
globs such as rm -i -- *.tmp.
Frequently asked questions
How do I find files older than 7 days in Linux?
find /path -type f -mtime +7. The number counts whole days of 24 hours and GNU find ignores the fraction, so +7 matches files last changed at least 8 days ago. Use -mmin +60 for minutes. There is no unit suffix: -mtime +7d is an error.
How do I find large files on Linux?
find / -xdev -type f -size +500M -exec ls -lh {} + lists files over 500 MiB on the root file system, without crossing into other mounts. For a sorted list: find / -xdev -type f -size +100M -printf '%s\t%p\n' 2>/dev/null | sort -rn | head.
How do I search for text in all files of a directory?
grep -rn 'text' /path searches recursively and prints file names and line numbers. Add -i to ignore case, -w to match whole words, --include='*.conf' to limit it to certain files, and -l to print only the names of matching files.
How do I delete files older than 30 days?
First look: find /path -type f -mtime +30 -print. When the list is right, run the same command with -delete instead of -print, and keep -delete at the very end. Put -type f in so directories are never touched.