Finding Files: find & xargs
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
find WHERE TESTS ACTIONWHERE 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.
find . -name "*.txt"
find projects -type d
find . -iname "*.CSV"./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
| Test | Matches |
|---|---|
-size +50k | Larger than 50 KB (M for MB, G for GB; - means smaller) |
-mtime +30 | Modified more than 30 days ago (-7 means within the last 7 days) |
-mmin -60 | Modified in the last 60 minutes |
-user www-data | Owned by user www-data |
-perm -4000 | Has the SUID bit set (and possibly others) |
-empty | Empty files and empty directories |
-maxdepth 1 | Do 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.
find . -type f -size +50k
find projects -name "*.log" -mtime +30
find . -maxdepth 1 -type f./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:
sudo find / -xdev -type f -perm -4000 2> /dev/null/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):
find projects -name "*.css" -exec wc -l {} \;
find projects -name "*.log" -exec ls -lh {} +
find projects -name "*.log" -mtime +30 -delete -print1 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.
find . -name "*.txt" | xargs wc -lwc: ./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:
find . -name "*.txt" -print0 | xargs -0 wc -l0 ./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
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 -ka 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 *.txtmay break when the current folder contains.txtfiles. Write-name "*.txt". - Putting
-deletefirst. It deletes everything it visits. Keep actions at the end, and test without them. - Plain
| xargswith odd file names. Use-print0 | xargs -0. - Searching
/without-xdev. find wanders into/procand network mounts and becomes slow and noisy.
Key points
find DIR tests action: tests such as-name,-type,-size,-mtime,-userand-permselect files.- Several tests together mean AND; use
-ofor OR and!for NOT. -exec cmd {} +runs a command on the results;-deletedeletes them; test first without the action.xargsturns stdin into arguments; usefind -print0 | xargs -0to handle spaces safely.locateis 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
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/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?
-type d matches directories; -type f matches regular files.
2. What does find /var/log -name "*.gz" -mtime +90 find?
+90 means more than 90 days; -90 would mean within the last 90.
3. Why use find ... -print0 | xargs -0?
Null-separated names cannot be split by spaces or newlines.
4. What is the difference between -exec cmd {} \; and -exec cmd {} +?
The + form is faster because it starts the command far fewer times.
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
