Finding Files: find & xargs

Linux · Lesson 15 of 32 7 min read Updated

ls shows one folder. When you need to search a whole tree, such as "every log file older than 30 days" or "every file over 100 MB" or "every PHP file owned by the wrong user", you use find. It walks through directories and tests every file against the conditions you give it. Then, with -exec or xargs, you can run a command on everything it found.

This lesson covers the tests you will use most, how to act on results safely, and how to handle file names with spaces correctly.

The shape of a find command

Text
find  WHERE  TESTS  ACTION

WHERE is one or more starting directories (. means the current one). Tests decide which files match. The action says what to do with them; if you give none, find prints their paths.

Terminal
find . -name "*.txt"
find projects -type d
find . -iname "*.CSV"
Output
./notes/meeting notes.txt
./notes/shopping.txt
./notes/todo.txt
projects
projects/shop-site
projects/shop-site/images
projects/shop-site/logs
./orders.csv

-name matches the file name with shell-style wildcards, and -iname ignores case. Always quote the pattern: unquoted, the shell might expand *.txt itself before find even runs. -type d matches directories, -type f regular files and -type l symbolic links.

Searching by size, age, owner and permissions

TestMatches
-size +50kLarger than 50 KB (M for MB, G for GB; - means smaller)
-mtime +30Modified more than 30 days ago (-7 means within the last 7 days)
-mmin -60Modified in the last 60 minutes
-user www-dataOwned by user www-data
-perm -4000Has the SUID bit set (and possibly others)
-emptyEmpty files and empty directories
-maxdepth 1Do not go deeper than one level

Tests placed one after another must all be true (an implicit AND). Use -o for OR and ! for NOT.

Terminal
find . -type f -size +50k
find projects -name "*.log" -mtime +30
find . -maxdepth 1 -type f
Output
./Documents/resume.pdf
projects/shop-site/logs/app-2026-10-01.log
./access.log
./app.conf
./auth.log
./orders.csv

The first finds big files, the second finds old logs, and the third lists only the files directly in the current folder, ignoring subfolders.

Searching system folders as a normal user produces many "Permission denied" lines. Hide them with 2> /dev/null, or search with sudo. A classic security check finds every program with the SUID bit:

Terminal
sudo find / -xdev -type f -perm -4000 2> /dev/null
Output
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/mount
/usr/bin/su
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/umount
/usr/bin/chsh
...

-xdev stops find from wandering into other mounted filesystems such as /proc. Compare the list with a known-good system: an unexpected SUID file is a warning sign.

Acting on results: -exec and -delete

-exec runs a command on each match. {} stands for the file name, and the command ends with \; (one run per file) or + (as many files as possible in one run, which is much faster):

Terminal
find projects -name "*.css" -exec wc -l {} \;
find projects -name "*.log" -exec ls -lh {} +
find projects -name "*.log" -mtime +30 -delete -print
Output
1 projects/shop-site/style.css
-rw-rw-r-- 1 priya priya 7 Sep  1 00:00 projects/shop-site/logs/app-2026-10-01.log
-rw-rw-r-- 1 priya priya 7 Oct 30 13:02 projects/shop-site/logs/app-2026-10-02.log
-rw-rw-r-- 1 priya priya 7 Oct 30 13:02 projects/shop-site/logs/app-2026-10-03.log
projects/shop-site/logs/app-2026-10-01.log

The last command deletes logs older than 30 days and prints each path as it goes. -exec also fixes permissions properly, something chmod -R cannot do on its own: find /var/www/shop -type d -exec chmod 755 {} + for folders and find /var/www/shop -type f -exec chmod 644 {} + for files.

xargs: turn a list into arguments

Many commands take file names as arguments but do not read them from stdin. xargs bridges the gap: it reads words from stdin and adds them to the end of a command.

Terminal
find . -name "*.txt" | xargs wc -l
Output
wc: ./notes/meeting: No such file or directory
wc: notes.txt: No such file or directory
 0 ./notes/shopping.txt
 3 ./notes/todo.txt
 3 total

This shows the classic xargs bug: xargs splits on spaces, so the file meeting notes.txt became two wrong names. The fix is to separate names with the null character, which can never appear in a file name. find -print0 writes names that way and xargs -0 reads them:

Terminal
find . -name "*.txt" -print0 | xargs -0 wc -l
Output
0 ./notes/meeting notes.txt
0 ./notes/shopping.txt
3 ./notes/todo.txt
3 total

Make -print0 | xargs -0 a habit whenever file names come from find.

More xargs options

Terminal
echo "a b c d e" | xargs -n 2 echo
printf 'pune\njaipur\n' | xargs -I {} echo "Backing up {}-store"
find . -name "*.jpg" -print0 | xargs -0 -P 4 -n 10 gzip -k
Output
a b
c d
e
Backing up pune-store
Backing up jaipur-store

-n 2 passes at most two arguments per run. -I {} runs the command once per input line and puts the line wherever {} appears. -P 4 runs up to four commands at the same time, which speeds up slow jobs such as compressing many files on a multi-core server.

Faster: locate

find reads the disk every time. locate searches a ready-made database of file names and answers instantly. On Ubuntu it comes from the plocate package (sudo apt install plocate). The database is updated once a day, so very new files may be missing until you run sudo updatedb.

Common mistakes

  • Unquoted patterns. find . -name *.txt may break when the current folder contains .txt files. Write -name "*.txt".
  • Putting -delete first. It deletes everything it visits. Keep actions at the end, and test without them.
  • Plain | xargs with odd file names. Use -print0 | xargs -0.
  • Searching / without -xdev. find wanders into /proc and network mounts and becomes slow and noisy.

Key points

  • find DIR tests action: tests such as -name, -type, -size, -mtime, -user and -perm select files.
  • Several tests together mean AND; use -o for OR and ! for NOT.
  • -exec cmd {} + runs a command on the results; -delete deletes them; test first without the action.
  • xargs turns stdin into arguments; use find -print0 | xargs -0 to handle spaces safely.
  • locate is instant but uses a database that is refreshed daily.

Exercise

Write find commands to: list all .html files under /var/www; find files in your home folder larger than 10 MB; find files in /etc changed in the last 2 days (hide permission errors); and count how many lines are in all .py files under ~/projects, safely, even if some names contain spaces.

Show solution
Terminal
find /var/www -type f -name "*.html"
find ~ -type f -size +10M
find /etc -type f -mtime -2 2> /dev/null
find ~/projects -name "*.py" -print0 | xargs -0 wc -l | tail -n 1
Output
/var/www/shop/index.html
/var/www/shop/menu.html
/home/priya/Downloads/ubuntu-26.04-live-server-amd64.iso
/etc/hosts
/etc/ld.so.cache
  412 total

-mtime -2 means "less than 2 days ago". tail -n 1 keeps only the total line from wc. Your results will depend on what is on your machine.

Quick Quiz

1. Which test finds directories only?

2. What does find /var/log -name "*.gz" -mtime +90 find?

3. Why use find ... -print0 | xargs -0?

4. What is the difference between -exec cmd {} \; and -exec cmd {} +?

Learn Linux with a mentor

Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call