CoursesLinux essentialsFinding files & commands

Finding files & commands

find, locate, which, whereis, xargs.

Beginner12 min · lesson 7 of 25

A running server is a filing cabinet with a few million drawers, and nobody bothered to label them. Some ordinary Tuesday you will need the one config file that decides how the web server boots. Or every log nobody has touched in a week, because the disk just filled up. Or something smaller and sharper: when you type python3, which of the several copies on this machine actually runs? A handful of small tools cover every shape of 'where is that thing?', and one of them, find, is strong enough to reach into the drawer and act on whatever it pulls out.

There are two ways to search a disk, and the difference matters. You can walk it live, opening every folder and checking each file as you go: always accurate, a little slow. Or you can look the answer up in a list somebody built earlier: instant, but only as fresh as that list. find does the first. locate does the second. Then which, whereis, and type answer a different question altogether, not 'where does this file sit' but 'which program runs when I type this word'.

Which tool answers your question
You need to find something. Which tool?
You know the name and want it now
locate
reads a prebuilt index; instant, but can be stale
You need files by age, size, or permission
find
walks the tree live; always current, and can act on matches
You typed a command; which file runs?
which / type
searches PATH in order; the first match wins

find, The Workhorse

find is the bloodhound. You give it a place to start and a description of what you want, and it walks the whole directory tree underneath (folders inside folders, like boxes inside boxes), keeping only the files that match every part of your description. It is wordy to type, and it reads the disk for real, so it is slower than a lookup. But it is never out of date, and it can be startlingly precise.

You build that description out of tests. -name matches the filename (wrap the wildcard, the * that stands for 'any characters here', in quotes so the shell, the program that reads your command, hands the * to find instead of expanding it first). -type f keeps regular files, -type d keeps directories. -mtime matches on how many days ago the file was last modified. -size matches on how big it is. -perm matches on permission bits. Stack as many as you like. A file has to pass all of them to make the list.

~/secopslog — bash
$ find /etc -type f -name "*.conf"
/etc/nsswitch.conf /etc/host.conf /etc/logrotate.conf /etc/sysctl.conf /etc/ld.so.conf /etc/nginx/nginx.conf /etc/ssh/sshd_config.d/10-hardening.conf /etc/systemd/system.conf

The same shape answers the operations questions. Which logs are old enough to clear out? Which files are eating the disk? Watch the + in front of each number. +7 means more than seven days old; +100M means bigger than 100 megabytes. A bare 7 would mean exactly seven days, to the day, which is almost never what you want.

~/secopslog — bash
$ find /var/log -type f -name "*.log" -mtime +7
/var/log/app/worker-2026-07-01.log /var/log/app/worker-2026-07-02.log /var/log/app/worker-2026-07-04.log
$ find /var -type f -size +100M 2>/dev/null
/var/lib/docker/overlay2/3f9a1c/merged/data/dump.sql /var/log/journal/9c1e0b/system.journal

That 2>/dev/null on the end throws away the 'Permission denied' noise find prints every time it bumps into a folder your user is not allowed to open, so you are left reading only the hits.

find As A Tripwire

For a DevSecOps (security and operations rolled into one job) engineer, find is half operations tool and half tripwire, the thin wire strung across the doorway that trips the moment someone steps over it. Two questions come up over and over. First: what has an unexpected SUID bit set? SUID (Set User ID) is a flag on a program that says 'run me with the file owner's powers, not the caller's'. It is why an ordinary user can run passwd to change their own password even though that edits a file only root can write. It is also exactly how an attacker climbs to root: copy a shell, set the SUID bit on the copy, and you have a root shell in your pocket. So you learn your machine's normal set once, and you watch for anything outside it.

~/secopslog — bash
$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/chsh /usr/bin/gpasswd /usr/bin/newgrp /usr/bin/passwd /usr/bin/sudo /usr/bin/su /usr/bin/mount /usr/bin/umount /usr/lib/openssh/ssh-keysign /usr/lib/dbus-1.0/dbus-daemon-launch-helper

Everything in that list is normal on a stock system. A SUID copy of bash sitting in /tmp, or a SUID binary tucked inside somebody's home directory, is not, and it is one of the first things an incident responder goes hunting for. The second question is about time: what changed recently? -mmin -60 means 'modified in the last sixty minutes', which is how you ask 'what did whoever was just on this box touch?'

~/secopslog — bash
$ find /etc -type f -mmin -60
/etc/passwd /etc/cron.d/apache-backup

Two files in /etc changed in the last hour, and you did not change them. A brand-new scheduled job you never wrote, sitting right next to a freshly edited /etc/passwd, is the exact shape of a backdoor being planted. find did not say so in words. It just put the right two files in front of your eyes.

Acting On What find Finds

So far find only points. The next step is to do something to every file it points at, and there are two roads. -exec runs a command for you, swapping {} for each filename. End it with \; and the command runs once per file; end it with + instead and find packs many filenames into a single run, which is far faster on a long list. The other road is xargs, a small conveyor belt: it takes the list of names find produced and feeds them, in batches, as arguments to another command.

~/secopslog — bash
$ # both print nothing on success find . -type f -name "*.sh" -exec chmod 750 {} + find /var/log/app -name "*.log" -mtime +30 -delete

The xargs road shines when the next command is a search of its own. Here find gathers every Python file and hands the pile to grep, whose -l flag prints the name of each file that contains the pattern instead of the matching lines themselves. This is the everyday 'which of my scripts import this thing?' question.

~/secopslog — bash
$ find . -type f -name "*.py" -print0 | xargs -0 grep -l "import os"
./app/storage.py ./app/tasks/backup.py
find piped to xargs breaks on spaces, and attackers know it
A plain find ... | xargs command splits its input on spaces and newlines. So a file named quarterly report.pdf arrives as two separate arguments, quarterly and report.pdf, and the command runs on the wrong paths. With rm on the end, that can wipe a file you never meant to touch. A filename is attacker-controlled data, and a cleverly named file can hijack a careless pipeline. Pair find -print0 with xargs -0, which split on the invisible null character (the one byte a filename can never contain), or use -exec and skip xargs altogether.
Run the find without the destructive part first
A find with -delete, or piped into xargs rm, acts on every single file the pattern matched. An over-broad -name, or a wrong starting path, can erase far more than you meant. Run the plain find on its own first and read the list of files it prints. That list is your dry run. Add -delete or the rm only once you have confirmed the list holds exactly what you mean to destroy, and nothing else.

locate, The Prebuilt Catalogue

locate is the library catalogue. Instead of walking every shelf, you look up one card and get the shelf number at once. A background job called updatedb walks the whole filesystem, usually once a day, and writes every path it sees into a database. locate only reads that database, so it answers in milliseconds no matter how big the disk is. On current Debian and Ubuntu the tool is plocate, and its database sits at /var/lib/plocate/plocate.db. It often is not installed until you ask for it (sudo apt install plocate).

~/secopslog — bash
$ locate nginx.conf
/etc/nginx/nginx.conf /usr/share/doc/nginx/examples/nginx.conf

The catch is that the catalogue is only as fresh as the last updatedb run. Write a file this minute and locate will swear it does not exist, because the database has not seen it yet. When that bites you, run updatedb by hand (it needs root, because it reads the whole disk). Watch the difference: the first lookup finds nothing, then the index is rebuilt, then the same lookup succeeds.

~/secopslog — bash
$ locate secrets.env sudo updatedb locate secrets.env
/home/deploy/app/config/secrets.env

Two security notes. First, locate honours permissions: before plocate shows you a path, it checks whether your account could actually open the folders leading to it, and hides the ones you could not reach anyway. So you cannot use it to peek into another user's private directories. Second, from the attacker's side, it is a blindingly fast way to surface things that should not be lying around. A single locate id_rsa or locate .env across a whole box turns up private keys and secret files in seconds, which is handy for you and every bit as handy for anyone who lands a shell on your server.

which, whereis, And type

The last question is a different animal. When you type ls, the shell does not guess. It walks a list of folders called PATH (an environment variable, which is just a named setting your shell keeps in memory), in order, and runs the first ls it finds. which shows you the winner: the exact file that will run. That matters the moment more than one copy exists, which is common with languages like Python or Node that people install several different ways.

~/secopslog — bash
$ which python3
/usr/bin/python3

Three cousins round out the picture. whereis casts a wider net, turning up the program, its manual page, and its source code if that is present. type is a shell built-in that knows things the file-searchers cannot, such as whether the name is really an alias or a shell function rather than a program sitting on disk. And command -v is the one to reach for inside scripts, because it is part of POSIX (the portability standard that keeps Unix-like systems behaving the same way) and works everywhere alike.

~/secopslog — bash
$ whereis ssh type ll command -v grep
ssh: /usr/bin/ssh /usr/share/man/man1/ssh.1.gz ll is aliased to `ls -alF' /usr/bin/grep

Here is where it turns into a security check. Nothing stops a folder earlier in PATH from holding its own ls, or python3, or git. If one does, that copy runs and the real one never gets a look. This is called shadowing, and it is a tidy way to hijack a command. The danger climbs when PATH includes a folder that ordinary users (or an intruder who got a foothold) can write to. A common own-goal is prepending a personal bin folder in your shell startup file:

~/.bashrc
# Anything in ~/bin now wins over system commands of the same name
export PATH="$HOME/bin:$PATH"

That line is handy, and it is also a loaded gun: whoever can drop a file into ~/bin now gets a say in every command you type. which -a prints every match in PATH order, not only the winner, so it is how you check who is really standing in line:

~/secopslog — bash
$ echo $PATH which -a python3
/home/deploy/bin:/usr/local/bin:/usr/bin:/bin /home/deploy/bin/python3 /usr/bin/python3

The python3 in /home/deploy/bin comes first, so that is the one that runs, and the genuine interpreter over in /usr/bin sits idle behind it. If you did not put that file there, someone else did, and you have found your problem.

One More Sweep Before You Log Off

Put the pieces together and find becomes a fast audit. A world-writable file is one that any account on the box can rewrite. If such a file is a script that root runs on a schedule, that is a clean path from any user straight to root, so it is worth a periodic sweep of the directories where system programs and configuration live.

~/secopslog — bash
$ find /etc /usr -type f -perm -0002 2>/dev/null
/usr/local/bin/deploy.sh

On a healthy system that command prints nothing at all. When it does print a path, you have a concrete thing to fix before you touch anything else: tighten the permissions, work out who loosened them, and check whether the file was changed while it sat open to the world.

Quick check
01You run find /data -name '*.tmp' | xargs rm to clean up temp files. Afterwards a colleague's important file three folders away is gone, yet an old temp file you meant to delete is still there. What most likely happened?
Correct — xargs splits on whitespace, so weekly report.tmp becomes two arguments; use find -print0 | xargs -0 (or -exec) to stay safe.
Incorrect — find is recursive by default; it walks every folder under the starting path.
Incorrect — rm happily takes many arguments at once; that is exactly what xargs feeds it.
Incorrect — There is no locate here; this pipeline searches live with find.
02Both find and locate can tell you where a file lives. What is the core difference in how they work?
Incorrect — that is backwards; locate is the one that reads a prebuilt index.
Correct — that trade-off is why a file created a minute ago shows up in find but not yet in locate.
Incorrect — they use entirely different mechanisms - find touches the disk, locate reads a cached database.
Incorrect — find searches any path you give it, up to and including the entire filesystem.
03find / -perm -4000 -type f 2>/dev/null returns the usual system binaries plus one extra line: /tmp/sh. What does that extra entry most likely mean?
Incorrect — a reboot may clear it, but right now a root-owned SUID shell is a live privilege-escalation risk.
Incorrect — any file can have the SUID bit set, which is precisely what makes this one dangerous.
Correct — SUID runs a program as its owner, so a SUID sh owned by root is a root shell for whoever executes it.
Incorrect — SUID changes which identity the program runs as, a real privilege change rather than a cosmetic one.

Try this

Work through “One More Sweep Before You Log Off” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: find piped to xargs breaks on spaces, and attackers know it. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related