Skip to content
elephantoo

Finding files: find, locate & which

Lesson 11 of 31 14 min read

Search by name, type, size, time and owner, act on results with -exec, and find commands on your PATH.


"Where did I put that config file?" "Which logs are eating my disk?" "Delete every .tmp file older than a week." The find command answers questions like these by walking a directory tree and testing every file against rules you give it. We'll also cover quicker tools for finding commands.

Practice tree (create it once):

Terminal
mkdir -p ~/practice/find && cd ~/practice/find
mkdir -p project/{src,docs,logs,node_modules/lib}
touch project/README.md project/src/{app.py,utils.py,Test.PY} project/docs/guide.md \
      project/node_modules/lib/index.md "project/docs/release notes.md"
head -c 2000000 /dev/zero > project/logs/big.log      # a 2 MB file of zero bytes
echo hi > project/logs/small.log
touch -d '40 days ago' project/logs/old.log           # pretend this is old
cd project

(A \ at the end of a line continues the command on the next line.)

The shape of a find command#

Output
find  WHERE  TESTS...  ACTION
find  .      -name "*.md"   -print
  • WHERE: one or more starting directories (. is here, / is everything, ~ is home).
  • TESTS: conditions like name, type, size and age. Several tests are ANDed together.
  • ACTION: what to do with matches. The default is -print (print the path).

find lists files in on-disk order, which may differ on your machine. Pipe to sort if you need a stable order.

Finding by name#

Terminal
find . -name "*.md"
Output
./docs/release notes.md
./docs/guide.md
./node_modules/lib/index.md
./README.md
Terminal
find . -iname "*.py"      # -iname: case-insensitive
Output
./src/Test.PY
./src/app.py
./src/utils.py

Always quote the pattern. Unquoted, the shell would expand *.md in the current directory first (to README.md), and find would only search for that one name.

Search the whole system for a file, hiding "Permission denied" noise:

Terminal
find / -name "sshd_config" 2>/dev/null

Finding by type#

Terminal
find . -type d
Output
.
./logs
./docs
./src
./node_modules
./node_modules/lib
TestMatches
-type fRegular files
-type dDirectories
-type lSymbolic links
-emptyEmpty files or directories
-maxdepth NDon't descend more than N levels (put it before the other tests)
Terminal
find . -maxdepth 1 -type f
Output
./README.md

Finding by size and age#

Terminal
find . -size +1M          # bigger than 1 MiB
find . -mtime +30         # modified more than 30 days ago
Output
./logs/big.log
./logs/old.log
TestMeaning
-size +100M / -size -10kMore than 100 MiB / less than 10 KiB (c bytes, k, M, G)
-mtime -1Modified within the last 24 hours
-mtime +7Modified more than 7 days ago
-mmin -15Modified within the last 15 minutes
-newer fileModified more recently than file
-user ada, -perm -o+wOwned by ada / writable by others

A classic disk-space hunt:

Terminal
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null

-xdev stays on one filesystem, so find doesn't wander into network mounts or /proc.

Combining and excluding#

Tests are ANDed. Use -o for OR, ! (or -not) for NOT, and escaped parentheses for grouping:

Terminal
find . -type f \( -name "*.py" -o -name "*.md" \)
find . -name "*.md" -not -path "*/node_modules/*"

For big folders you don't want to descend into at all, -prune is faster than filtering afterwards:

Terminal
find . -path ./node_modules -prune -o -name "*.md" -print
Output
./docs/release notes.md
./docs/guide.md
./README.md

Read it as: "if the path is ./node_modules, prune it (don't go in); otherwise print .md files". The explicit -print at the end is required here.

Acting on what you find#

-exec

Terminal
find . -name "*.log" -size +1k -exec ls -lh {} \;
Output
-rw-r--r-- 1 ada ada 2.0M Oct  1 10:30 ./logs/big.log

{} is replaced by each path, and \; ends the command (escaped so the shell doesn't eat the ;). Ending with + instead of \; passes many paths to one command, which is much faster:

Terminal
find . -name "*.md" -exec wc -l {} +
find . -type f -name "*.sh" -exec chmod +x {} +

-delete

Terminal
find /tmp/myapp -name "*.tmp" -mtime +7 -print        # 1. preview
find /tmp/myapp -name "*.tmp" -mtime +7 -delete       # 2. then delete

Always run the command with -print first and read the list. Put -delete last: find . -delete -name "*.tmp" deletes everything, because actions run in order.

Piping to xargs safely

File names can contain spaces (and even newlines). -print0 separates names with a NUL byte, which xargs -0 understands:

Terminal
find . -name "*.md" -print0 | xargs -0 ls -1
Output
./README.md
./docs/guide.md
./docs/release notes.md
./node_modules/lib/index.md

Without -print0/-0, release notes.md would be split into two bogus names.

locate: instant searches from an index#

find walks the disk live, which can be slow. locate searches a prebuilt database instead:

Terminal
sudo apt install plocate      # Fedora: sudo dnf install plocate
sudo updatedb                 # build/refresh the index (also runs daily)
locate sshd_config
locate -i readme | head

The trade-off: files created since the last updatedb won't show up.

Finding commands: which, type, whereis#

When you type a command name, the shell searches the directories listed in the PATH variable, in order. To see which file wins:

Terminal
which python3
type -a ls
whereis -b bash
Output
/usr/bin/python3
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls
ls is /bin/ls
bash: /usr/bin/bash
  • which prints the first match on PATH.
  • type (a Bash built-in) also reveals aliases, functions and built-ins, so it's the most truthful answer.
  • whereis lists the binary (-b), and without options also source and man pages.
  • command -v git is the portable way for scripts to check whether a tool is installed: it prints the path, or nothing with a non-zero exit status.

Common mistakes#

  • Unquoted patterns: find . -name *.txt.
  • Forgetting the starting directory on older systems (find -name x works in GNU find but not everywhere).
  • Using -delete without previewing, or placing it before the tests.
  • Misreading sizes: -size -1k means "rounded up to 1 KiB blocks, fewer than 1", i.e. empty files only. Use c (bytes) for precision: -size -1000c.
  • Parsing ls output in scripts instead of using find ... -print0.

What's next#

find locates files by their properties. To search inside files for text and patterns, you need grep and regular expressions.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Which command finds all regular files ending in .log under /var/log that are larger than 100 MB?

  2. 2.Why should you quote the pattern in find . -name "*.txt"?

  3. 3.You type python3 and want to know exactly which file on disk runs. Which command tells you?

Finished reading?

Mark this lesson complete to track your progress.