Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

find: search for files efficiently

By updated

Improve this page

When you manage servers or development environments with deep trees, you often need to quickly find a huge log, a lost script or a specific directory. find walks the file system in real time and lets you select files (name, type, size, date, permissions) then act on them (print, audit, run a command, clean up).

What you will learn

  • Target files by name, type, size, date, permissions
  • Combine criteria (AND/OR/NOT, parentheses)
  • Apply an action safely (-print → then -exec/-delete)

The find command in the Linux ecosystem

find belongs to the file search and processing commands of Linux. Each tool has its speciality, and find is the one that searches by attributes:

CommandSpecialityWhen to use it
findSearch for files by attributesFind by name, size, date, permissions
locateUltra-fast search by nameWhen speed matters most (indexed database)
grepSearch in the contentFind a string in files
which / whereisLocate executablesFind where a program is installed
xargsRun commands in bulkPass the results of find to a command
awkProcess tabular dataExtract/transform columns
sedEdit files as a streamReplace text, delete lines
cutExtract columnsSplit by delimiter or position

Frequent combinations:

Terminal window
# find + grep: search a pattern in some files
find /var/log -name '*.log' -exec grep -l "error" {} +
# find + xargs: bulk processing (robust with spaces)
find . -name '*.tmp' -print0 | xargs -0 rm -f
# locate + grep: filter the results of locate
locate '*.conf' | grep nginx

Understand the find command in 2 minutes

The mental model below sums up how find works: where to search, what to keep, what to do.

🧠 Mental model: How find works

find = Where to search → What to keep → What to do

find starts from a directory, keeps the files that pass its filters (-name, -type, -size, -mtime) then applies an action (-print, -delete, -exec); -maxdepth, -mindepth, -prune and -depth control the walk.findWHERE TO SEARCH/var/log · . · /homeWHAT TO KEEP-name · -type · -size · -mtimeWHAT TO DO-print · -delete · -execwalk control-maxdepth · -mindepth · -prune · -depth
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Key points

  • find walks a directory and all its subdirectories
  • It keeps the files that match your criteria (name, type, date, size)
  • Several criteria = AND (both must match)
  • If you use OR (-o), think of the parentheses
  • You can run a command on the files found (-exec)
  • You can delete files (-delete) BUT always check first with -print

Golden rules

1
Always test before using destructive commands Start with -print, and only then -delete.
2
Handle "odd" names Put wildcards between quotes, or escape them: '*.log' or \*.log

Essential vocabulary

-name '*.log'
Files whose name matches the pattern
-type f / -type d
File (f) or directory (d)
-mtime +7
Modified more than 7 days ago
-size +100M
Size above 100 MiB
-maxdepth 2
Do not go down more than 2 levels
-print / -delete
Print the results / Delete
📚 Going further: 12 advanced options
-iname
Like -name but case-insensitive
-path / -ipath
Pattern on the full path
-mmin -60
Modified less than 60 minutes ago
-perm 644
Exact permissions
-user / -group
By owner or group
-prune
Exclude a subtree
-xdev
Do not cross mount points
-exec cmd {} \;
Run for each file
-exec cmd {} +
Run in batches (faster)
-ok cmd {} \;
Like -exec with confirmation
-printf FORMAT
Custom formatted output
-quit
Stop at the first result

Unlike locate (pre-computed index), find does not depend on a database: the result reflects the current state of the disk.

Minimal syntax

The order of the three groups is not decorative: find evaluates the arguments from left to right, and a filter placed after an action no longer filters anything. It is the number one cause of accidental deletions, such as find /tmp -delete -name '*.tmp', which empties the whole of /tmp instead of targeting the .tmp files. Remember the rule: path, then filters, then action, always in this order.

Terminal window
find [CHEMIN...] [FILTRES...] [ACTIONS...]

The 3 parts of a find command

The find command always follows the same logic in three steps: a starting point, filters, an action:

StepQuestionExamplesRequired?
Where to searchWhich directory to start from?/var/log, ., /homeImplicit (default: .)
Which results to keepWhich files match my criteria?-name '*.log', -type f, -size +100MNo (default: everything)
What to doWhich action to run on the results?-print, -delete, -exec rm {} +No (default: -print)

Where to search (optional): it is the starting point. find walks this directory and all its subdirectories recursively. Without a path, GNU find uses . (the current directory), but the BSD and macOS implementations require an explicit path: get into the habit of always writing it.

Which results to keep (optional): these are the filters. Without a filter, find returns everything. Several criteria can be combined: by default they are linked by AND (all of them must match).

What to do (optional): it is the final action. Without an explicit action, find simply prints the paths (-print). For destructive actions (-delete, -exec rm), always test with -print first.

Filters: target your files precisely

Filters narrow the results down to the files you care about. Here are the most used ones, starting with the name tests and their case sensitivity:

FilterWhat it testsExample
-name 'motif'File name (case-sensitive)-name '*.log'
-iname 'motif'File name (case-insensitive)-iname '*.LOG'

Always put the pattern between quotes: -name '*.log'. Otherwise the shell interprets * before find and you get unexpected results.

FilterWhat it testsExample
-type fFiles only-type f
-type dDirectories only-type d
-size +100MSize above 100 MB-size +100M
-size -1kSize below 1 KB-size -1k
-mtime -7Modified less than 7 days ago-mtime -7
-mtime +30Modified more than 30 days ago-mtime +30
-user aliceBelongs to the user alice-user alice
-perm 644Permissions exactly 644-perm 644
-perm -644At least these bits (read/write)-perm -644
-perm /222At least one write bit-perm /222

Note on sizes, the rounding trap: -size always rounds up to the unit. A 500-byte file therefore counts as 1k, and find . -size -1k only returns 0-byte files, never small files. Same effect with M: -size -1M returns almost nothing. To reason in real size, use the c unit (bytes): find . -type f -size -1000c. The available units are b (512-byte blocks, the default), c (bytes), w (2-byte words), k (kibibytes), M (mebibytes) and G (gibibytes): they are powers of 1024, not decimal megabytes.

Combine filters, AND / OR / NOT:

By default, filters are combined with AND (all of them must match), without writing any operator:

Terminal window
# .log files AND larger than 10 MB (both conditions)
find /var/log -type f -name '*.log' -size +10M

Tip: add -type f to match files only (it avoids the noise of directories).

To use OR, add -o and parentheses (escaped), so that the grouping is explicit:

Terminal window
# .log OR .txt files
find /var/log -type f \( -name '*.log' -o -name '*.txt' \)

Why \( and \)? The shell interprets bare parentheses. You escape them (\( \)) or put them between quotes ('(' ')').

To exclude by name, use ! or -not, the negation:

Terminal window
# Every file EXCEPT those starting with .git
find . -type f ! -name '.git*'

Watch out: this does NOT exclude the .git/ directory from the walk. find still goes down into it and prints its content, since .git/config is named config and does not start with .git: only the directory itself disappears from the list, and the walk still costs time on large repositories. To really skip a directory, use -prune:

Terminal window
# Exclude the .git directory from the search (correct)
find . -name .git -prune -o -type f -print

Actions: what to do with the files found?

Once the files are filtered, you can act on them. The first two actions are risk-free, the next three modify the disk. The important distinction is between -delete and -exec rm: -delete is internal to find, it launches no process and correctly handles exotic names, but it automatically turns on -depth, which makes it incompatible with -prune. If your command contains -prune, go through -exec rm {} + or an xargs pipeline.

ActionWhat it doesExample
-printPrints the path (default)-print
-lsPrints in ls -l format-ls
-deleteDeletes the file-delete
-exec cmd {} \;Runs cmd on each file-exec chmod 644 {} \;
-exec cmd {} +Runs cmd with every file-exec rm {} +

Golden rule: always preview before destroying

Terminal window
# DANGEROUS: deletes directly, without any check
find /tmp -name '*.tmp' -delete
# SAFE: preview first
find /tmp -name '*.tmp' -print
# Check the list, then only:
find /tmp -name '*.tmp' -delete

Robust alternative (handles spaces and special characters in names):

Terminal window
find . -type f -name '*.tmp' -print0 | xargs -0 rm -f

-exec {} \; vs -exec {} +: the difference matters for performance, because it sets the number of processes:

  • \;: launches one command per file (slow with many files)
  • +: groups the files and launches a single command (fast, recommended)
Terminal window
# Slow: 1000 files = 1000 calls to chmod
find . -type f -exec chmod 644 {} \;
# Fast: 1000 files = a few calls to chmod
find . -type f -exec chmod 644 {} +

Control the walk: -depth and -prune

By default, find walks the tree from top to bottom. Two options change this behaviour, -depth and -prune, plus the depth limits.

-depth processes the content of a directory before the directory itself:

Terminal window
# Without -depth: /tmp is listed BEFORE its content
find /tmp -type d
# With -depth: /tmp is listed AFTER its content
find /tmp -depth -type d

It is automatic with -delete, to avoid deleting a directory before its content. A direct and often ignored consequence: since -depth neutralises -prune, a command that mixes -prune and -delete cannot protect the directory you thought you were excluding. GNU find 4.10 actually refuses it, with return code 1 and without deleting anything:

find: The -delete action automatically turns on -depth, but -prune does nothing when -depth is in effect. If you want to carry on anyway, just explicitly use the -depth option.

Adding -depth explicitly silences this refusal, and the exclusion is then ignored.

-prune stops the descent into a directory (useful to exclude directories):

Terminal window
# Exclude the node_modules directory from the search
find . -name 'node_modules' -prune -o -name '*.js' -print
# Exclude several directories
find . \( -name 'node_modules' -o -name '.git' \) -prune -o -type f -print

How to read -prune: "If you find this directory, do not go into it (-prune), otherwise (-o) apply the rest."

OptionEffectUse case
-depthProcesses children before parentsRecursive deletion
-pruneDoes not enter the directoryExclude node_modules, .git
-maxdepth NLimits the depthfind . -maxdepth 1 (current level)
-mindepth NIgnores the first N levelsfind . -mindepth 2 (subdirectories)

Practical examples:

Terminal window
# List only the immediate content (no recursion)
find /var/log -maxdepth 1 -type f -name '*.log'
# Search from the 2nd level on (skip the root directory)
find . -mindepth 2 -type f -name '*.conf'
# Combine both: search between level 2 and 4
find /home -mindepth 2 -maxdepth 4 -type d -name '.ssh'
# Delete empty directories (depth needed to process children first)
find /tmp -depth -type d -empty -delete
# List the files of the current level only (equivalent of ls)
find . -maxdepth 1 -type f

Typical errors (and fixes)

These four errors have two origins. The first comes from the shell, which expands * before find receives it: rerun the command prefixed with echo to see the arguments actually passed, and if echo find -name *.log prints a list of files instead of the pattern, you have found the culprit. The other three come from the grammar of find itself: operator precedence, the difference between filtering the output and preventing the descent, and the missing type filter.

ErrorCauseFix
find -name *.log finds nothingThe shell interpreted * before findPut it between quotes: -name '*.log'
-o does not work as expectedOperator precedenceAdd parentheses: \( ... -o ... \)
"I excluded .git but find searches it"! -name does not stop the descentUse -prune: -name .git -prune -o ...
Results include directoriesNo -type filterAdd -type f for files only

Common search patterns

Now that you understand the logic of find, here are ten ready-to-use patterns. Each one gives its general formula and a concrete example.

Search by exact name

-name only compares the last component of the path, never the full path. Searching for -name "syslog" therefore returns /var/log/syslog as well as /var/log/journal/syslog, but -name "log/syslog" returns nothing at all. To reason on the whole path, -path is the one to use.

Terminal window
find <chemin> -name "<nom>" # formula
find /var/log -name "syslog" # example
  • chemin: starting point of the search (default: .)
  • nom: exact name or glob pattern

Search by pattern (glob)

The glob of -name differs from the shell's on one surprising point: the * of find crosses dots, so -name "*.log" also returns hidden files such as .hidden.log. On the other hand, it never crosses the / separator, which guarantees that the pattern stays confined to the file name.

Terminal window
find <chemin> -name "*.ext" # formula
find . -name "*.log" # example
  • *: wildcard, any characters
  • always between quotes, otherwise the shell interprets the *

Filter by type

Adding -type f is not cosmetic: without it, an -exec or a -delete also applies to directories and symbolic links carrying the same pattern. The case of -type l deserves special attention: find tests the link itself, not its target, unless you add the -L option at the start of the command.

Terminal window
find <chemin> -type <f|d|l> # formula
find /etc -type d -name "*.d" # example
  • f: regular file, d: directory, l: symbolic link

Filter by size

This is the filter where the rounding up to the unit described above does the most damage. For a disk space audit, -size +100M works perfectly since you are looking for large files. To track small files, you must switch to bytes with the c suffix, otherwise you will only get empty files.

Terminal window
find <chemin> -type f -size <+|->N<c|k|M|G> # formula
find /var/log -type f -size +100M # example
  • +/-: larger / smaller than
  • units: c bytes, k KiB, M MiB, G GiB (powers of 1024)

Filter by modification date

-mtime reasons in 24-hour slices, counted from the moment the command starts, not from midnight. "Modified more than 7 days ago" therefore means "more than 7 times 24 hours ago", which does not match the calendar week. When you clean up logs with a dated retention policy, add the -daystart option to align the calculation on the start of the current day. For short durations, -mmin works in minutes.

Terminal window
find <chemin> -mtime <+|->N # formula
find /tmp -type f -mtime +30 # example
  • +N: modified more than N days ago
  • -N: modified less than N days ago

Run a command (-exec)

-exec launches the program directly, without going through a shell. It protects against hostile file names, but also forbids pipes, redirections and wildcards in the command run: -exec grep motif {} > sortie.txt \; redirects the output of find, not that of grep. For real shell, you must spell out -exec sh -c '...' _ {} \;. Careful variant: -ok replaces -exec and asks for a confirmation before each run.

Terminal window
find <chemin> <tests> -exec <cmd> {} \; # formula
find . -name "*.bak" -exec rm {} \; # example
  • {}: replaced with the file name
  • \;: ends the command (escaped)

Run in batches (-exec +)

The performance gain comes from the number of processes created: \; launches one per file, + launches one per pack of arguments fitting within the system limit. Two caveats to know before generalising. First, {} must be the last argument of the command, which rules out invocations such as cp {} /destination. Next, the output changes: wc -l in + mode adds a total line, absent in \; mode.

Terminal window
find <chemin> <tests> -exec <cmd> {} + # formula
find . -name "*.txt" -exec wc -l {} + # example
  • +: aggregates the files (like xargs)

Safe pipeline with xargs -0

The danger of a find | xargs pipeline without -print0 is not theoretical: xargs splits on spaces and on newlines, and a Unix file name can legally contain both. A file named rapport annuel.pdf becomes two arguments, rapport and annuel.pdf, and the command applies to targets that do not exist or, worse, that exist elsewhere. The null character is the only byte forbidden in a file name, hence the only reliable separator.

Terminal window
find <chemin> <tests> -print0 | xargs -0 <cmd> # formula
find . -name "*.pdf" -print0 | xargs -0 mv -t ~/backup/ # example
  • -print0: separates the results with a null character
  • xargs -0: reads that same null separator

Safe deletion

The two-step check has a limit worth knowing: between the -print and the -delete, the tree may have changed. On an active server, prefer to freeze the list in a file, reread it, then delete from that list rather than rerunning find. Also check that the starting path is really the one you think, a typo on /var instead of /var/tmp cannot be undone.

Terminal window
find <chemin> <tests> -print # step 1: preview
find <chemin> <tests> -delete # step 2: delete
  • Never -delete without running -print first.

Exclude a directory (-prune)

The construction reads like a switch: condition -prune -o tests -print. When the condition is true, -prune returns true, the -o short-circuits the rest and find does not enter the directory. The final -print is mandatory here: without it, the implicit -print action would also apply to the -prune branch and the excluded directories would show up anyway. It is the most frequent error with this syntax.

Terminal window
find <chemin> -path "*/<dossier>" -prune -o <tests> -print # formula
find . -path "./node_modules" -prune -o -name "*.js" -print # example
  • -prune: stops the descent into the targeted directory
  • -o: applies the tests to the rest of the tree

Pitfalls to avoid

Six errors come back constantly with find. Some are destructive, -delete does not forgive, and neither does a careless pattern.

-name distinguishes uppercase from lowercase.

Terminal window
find . -name "Report.txt" # WRONG: does not find "report.txt"

Symptom: the search returns nothing although the file exists. Cause: -name distinguishes uppercase from lowercase.

Terminal window
find . -iname "report.txt" # RIGHT: -iname ignores case

File names with spaces

A space in a name breaks a pipeline to xargs.

Terminal window
find . -name "*.txt" | xargs rm # WRONG: breaks on "mon fichier.txt"

Symptom: No such file error or the wrong files deleted. Cause: xargs splits on spaces.

Terminal window
find . -name "*.txt" -print0 | xargs -0 rm # RIGHT: null separator

OR without parentheses

The implicit AND binds tighter than -o, which changes the meaning of the expression.

Terminal window
find . -name "*.log" -o -name "*.txt" -mtime +7 # WRONG: -mtime only applies to the .txt files

Symptom: -mtime only filters part of the results. Cause: operator precedence groups -mtime with the second -name.

Terminal window
find . \( -name "*.log" -o -name "*.txt" \) -mtime +7 # RIGHT: escaped parentheses

-delete without checking

-delete acts immediately, without any preview.

Terminal window
find /var -name "*.tmp" -delete # WRONG: run directly, without any check

Symptom: important files deleted by mistake. Cause: an overly broad pattern applied without a preview.

Terminal window
find /var -name "*.tmp" -print # RIGHT: check first, then replace -print with -delete

mtime / ctime / atime confusion

Three timestamps coexist, and none of them is the creation date that find could use.

Terminal window
find . -mtime -1 # WRONG: does not reflect the "creation date"

Symptom: copied files show up as old. Cause: cp sometimes preserves the original mtime, which remains the modification date of the content. The ctime (change date of the inode) is then the right lead, since it is updated on every copy, every chmod and every rename.

Terminal window
# -mtime = content, -ctime = inode metadata, -atime = last access
find . -ctime -1

A nuance to know: modern file systems (ext4, XFS, Btrfs) do record a creation date (birth time), readable with stat --printf='%w\n' fichier. But find cannot always read it, support for the -newerBt predicate depends on how the binary was built. On Debian 13 as on AlmaLinux 10 (findutils 4.10), the command answers This system does not provide a way to find the birth time of a file, whereas stat does print that date: do not build a retention script on this date without testing it on the target machine.

Regex instead of glob

-name speaks glob, not regex.

Terminal window
find . -name ".*\.log$" # WRONG: returns nothing

Symptom: no result. Cause: -name uses globs (*, ?), not regular expressions.

Terminal window
find . -regex ".*\.log$" # RIGHT: -regex for a real regex (or simply -name "*.log")

Hands-on lab

Nothing replaces practice. Create the test environment below, then go through the eight steps. At the end, clean up with rm -rf ~/find-lab.

Prepare the ground

This script creates a tree in ~/find-lab with files of varied sizes, dates and permissions, including names containing spaces.

Terminal window
# Create the lab
mkdir -p ~/find-lab/{logs,config,data,backup}
cd ~/find-lab
# Log files of different sizes
dd if=/dev/zero of=logs/app.log bs=1M count=5 2>/dev/null
dd if=/dev/zero of=logs/error.log bs=1K count=100 2>/dev/null
dd if=/dev/zero of=logs/debug.log bs=1M count=20 2>/dev/null
# Config files
echo "port=8080" > config/app.conf
echo "debug=true" > config/debug.conf
# Files with spaces in the name
touch "data/mon fichier.txt"
touch "data/rapport 2024.pdf"
# Files with different dates
touch -d "30 days ago" backup/old-backup.tar
touch -d "7 days ago" backup/weekly.tar
touch backup/daily.tar
# Varied permissions
chmod 777 logs/debug.log
chmod 644 config/app.conf
echo "Lab créé dans ~/find-lab"
ls -laR ~/find-lab

The eight steps

Each step adds a filter or an action to the previous one, up to the controlled deletion of step 8. Always compare the number of lines returned with what the lab actually contains: it is by noticing that a command returns more results than expected that you spot a misplaced filter. Steps 7 and 8 are the most important, they cover the two situations that most often break production scripts, names with spaces and unchecked deletions.

  1. Find every .log file. List every file with the .log extension in the lab.

    Terminal window
    find ~/find-lab -name "*.log"

    -name uses a glob pattern; * matches any characters. To ignore case: find ~/find-lab -iname "*.LOG".

  2. Filter by size. Find the files larger than 1 MB.

    Terminal window
    find ~/find-lab -type f -size +1M

    +1M means "more than 1 mebibyte", the + indicates "greater than". Expected result: app.log and debug.log.

  3. Old files. Find the files modified more than 7 days ago.

    Terminal window
    find ~/find-lab -type f -mtime +7

    +7 means "modified more than 7 days ago"; -7 would be "less than 7 days". Only old-backup.tar comes out: weekly.tar is exactly 7 days old, which is not "more than 7".

  4. Combine size AND date. Find the files larger than 1 MB modified less than 1 day ago.

    Terminal window
    find ~/find-lab -type f -size +1M -mtime -1

    By default, criteria combine with AND, no need to write -a.

  5. .log OR .conf files. Find the .log or .conf files.

    Terminal window
    find ~/find-lab \( -name "*.log" -o -name "*.conf" \)

    -o is OR; the escaped parentheses (\( \)) group the conditions.

  6. Run a command on each file. Print the readable size of each .log.

    Terminal window
    find ~/find-lab -name "*.log" -exec ls -lh {} \;

    {} is replaced with the file name; \; ends the command. Faster version: -exec ls -lh {} +.

  7. Handle names with spaces. List the files of the data/ directory in detail.

    Terminal window
    find ~/find-lab/data -type f -print0 | xargs -0 ls -la

    -print0 and xargs -0 use the null character: mon fichier.txt stays a single argument.

  8. Delete safely. Delete the .tar files older than 7 days, preview first, delete next.

    Terminal window
    # Step 1: preview
    find ~/find-lab/backup -name "*.tar" -mtime +7 -print
    # Step 2: once the list is checked
    find ~/find-lab/backup -name "*.tar" -mtime +7 -delete

    Never use -delete without checking the list with -print.

Progressive exercises

Eight exercises to validate your mastery. Search on your own first, then unfold the solution to check yourself against the lab.

Foundation level

These three exercises cover a single filter at a time. If you get stuck, the answer always fits in a short option such as -maxdepth, -empty or -mtime: look for the dedicated option rather than composing a complex expression, find almost always offers one.

Exercise 1 - Find the .conf files. List the .conf files directly in /etc, without going into the subdirectories. Hint: -maxdepth 1 limits the depth.

Show the solution
Terminal window
find /etc -maxdepth 1 -name "*.conf"

-maxdepth 1 restricts the search to the starting directory, without recursion.

Exercise 2 - Empty files. Find every empty file (0 bytes) in /tmp. Hint: there is an option dedicated to empty files.

Show the solution
Terminal window
find /tmp -type f -empty

-empty is more readable than -size 0 and also works for empty directories.

Exercise 3 - Recent files. Find the files of your home directory modified in the last 24 hours. Hint: -mtime with a negative number.

Show the solution
Terminal window
find ~ -type f -mtime -1

-mtime -1 means "modified less than 1 day ago".

Composition level

Here you move on to combining criteria, and it is operator precedence that traps you. Remember that the implicit AND binds tighter than -o: as soon as an OR appears in a command that contains other filters, the escaped parentheses become mandatory, or the result is silently wrong.

Exercise 4 - Size and extension. Find the .log files larger than 10 MB in /var/log. Hint: combine -name and -size.

Show the solution
Terminal window
find /var/log -type f -name "*.log" -size +10M

Criteria combine with AND by default, no need to write -a.

Exercise 5 - Logical OR. Find the .jpg or .png files in ~/Images. Hint: -o with escaped parentheses.

Show the solution
Terminal window
find ~/Images -type f \( -name "*.jpg" -o -name "*.png" \)

The parentheses group the OR; without them, the logic would be wrong.

Industrialisation level

At this stage, find no longer looks, it acts. The two exercises oppose the two available mechanisms, the built-in -exec and the xargs pipeline. On large trees, measure the difference with time: measured on 10,000 files, \; takes 5.6 s against 0.09 s for +.

Exercise 6 - Run ls -lh on the results. Print the readable size of each file larger than 100 MB in /var. Hint: -exec with ls -lh.

Show the solution
Terminal window
find /var -type f -size +100M -exec ls -lh {} \;

{} is replaced with each file found; \; ends the command.

Exercise 7 - Safe xargs pipeline. Count the total number of lines of every .py file of a project, even with spaces in the names. Hint: -print0 then xargs -0.

Show the solution
Terminal window
find . -name "*.py" -print0 | xargs -0 wc -l

-print0 and xargs -0 handle names with spaces safely.

Safety level

The last exercise is the only destructive one, and that is on purpose. Run it first on a throwaway directory of your own, never directly on the /tmp of a shared server: other processes drop working files there whose disappearance causes failures that are hard to link back to your command.

Exercise 8 - Safe cleanup. Delete the .tmp files older than 7 days in /tmp, showing first what will be deleted. Hint: two commands, -print then -delete.

Show the solution
Terminal window
# Step 1: preview
find /tmp -name "*.tmp" -mtime +7 -print
# Step 2: once the list is checked
find /tmp -name "*.tmp" -mtime +7 -delete

Always preview with -print before using -delete.

Troubleshooting

When find does not return what you expect, the problem most often comes from quoting or permissions. Here is how to diagnose it.

Diagnostic methods

The method is to remove the filters one by one until you get results, then put back the last one removed: it is the culprit. The last command of the block deserves a word, it redirects stderr to stdout to filter the Permission denied messages. Without this 2>&1 redirection, grep sees nothing because the errors of find do not go through standard output.

Terminal window
# Remove the filters one by one to isolate the problem
find /chemin
# Add an explicit -print to see what is processed
find /chemin -name "*.log" -print
# Check the quoting (the shell may interpret * before find)
find /chemin -name "*.log"
# Spot the directories where find has no rights
find /chemin 2>&1 | grep -i permission

Frequent errors

The Fix column sometimes suggests 2>/dev/null, to handle with care: hiding permission errors is comfortable for an interactive search, but in a script it also hides the real failures, a full disk or a vanished mount point. In a scheduled script, keep the errors visible and rather check the return code of find, which is 1 as soon as a directory could not be walked.

Error messageProbable causeFix
find: '/root': Permission deniedNo read right on the directoryUse sudo, or hide the errors: find /chemin 2>/dev/null
find: '/chemin': No such file or directoryThe starting path does not existCheck the path with ls, check the spelling
find: missing argument to '-exec'\; or + forgotten at the end of -execEnd the command: -exec ls -l {} \;

find cheat sheet

The four tables that follow serve as a reminder once the guide is absorbed. They are organised in the order in which you build a command, walk then filters then actions then composition, which lets you read them from top to bottom while writing.

Walk options

These options go before the path or just after, and they set the extent of the walk rather than the selection. -xdev is the one most often forgotten: without it, a find / crosses NFS mounts, backup partitions and /proc, which turns a thirty-second search into several minutes.

OptionRoleExample
-maxdepth NLimits the search depthfind . -maxdepth 2 -name "*.txt"
-mindepth NIgnores the first N levelsfind . -mindepth 1 -type d
-LFollows symbolic linksfind -L /opt -name "*.conf"
-xdevStays on the same file systemfind / -xdev -size +100M

Filters

Filters stack freely and combine with an implicit AND. Two rows deserve special attention: -path compares the full path where -name only sees the name, and -newer compares with the modification date of a witness file, which gives a handy way to find everything that changed since a deployment by dropping a marker file before the operation.

FilterRoleExample
-name "*.ext"Name with glob (case-sensitive)-name "*.log"
-iname "*.ext"Name with glob (case-insensitive)-iname "*.JPG"
-type f / -type dFiles / directories only-type f
-path "*pattern*"Pattern on the full path-path "*/test/*"
-size +100M / -size -1kLarger / smaller than-size +1G
-emptyEmpty files or directories-type f -empty
-mtime +7 / -mtime -1Modified more / less than N days ago-mtime +30
-newer fichierNewer than a reference file-newer ref.txt
-perm 644Exact permissions-perm 755
-user bobBelongs to a user-user root

Actions

A single action per command in everyday practice, but nothing forces it: find . -name '*.log' -print -delete prints then deletes in the same pass. Avoid this form, it gives the illusion of control whereas the deletion has already happened when you read the output.

ActionRoleExample
-printPrints the path (default)-print
-print0Prints separated by a null character-print0 | xargs -0
-exec cmd {} \;Runs the command for each file-exec rm {} \;
-exec cmd {} +Runs the command in batches-exec ls {} +
-deleteDeletes the files found-mtime +30 -delete

Composition

The precedence rule fits in one sentence: AND (-a, implicit) binds tighter than OR (-o), exactly as multiplication binds tighter than addition. As soon as an -o lives alongside another filter, escaped parentheses are mandatory, otherwise find groups the terms differently from how you read them.

OperatorRoleExample
-aAND (implicit between two criteria)-name "*.log" -a -size +1M
-oOR-name "*.jpg" -o -name "*.png"
\( … \)Grouping of conditions\( -name "*.jpg" -o -name "*.png" \)
-pruneExcludes a subtree from the walk-path "./vendor" -prune

Mastery checklist

You master find when you can tick each of these points, from -name to -prune and safe deletion:

  • I can use -name and -iname
  • I can filter by -type (f, d, l)
  • I can use -size with +/- and the units
  • I can use -mtime, -ctime, -atime
  • I can combine criteria with -o and parentheses
  • I can use -exec {} \; and -exec {} +
  • I can secure a pipeline with -print0 \| xargs -0
  • I can secure -delete by running -print first
  • I can exclude a directory with -prune
  • I can diagnose a search that returns nothing

Conclusion

The find command is an essential tool for anyone who regularly works on the command line. Thanks to its flexibility and rich features, it lets you find, filter and handle files efficiently in any tree. Whether for a quick search, an automated cleanup or a permissions audit, mastering find saves precious time and strengthens the quality of your scripts and system work.

Key points

  • find walks the disk in real time: path, then filters, then action, always in this order.
  • The pattern of -name goes between quotes, otherwise the shell expands it before find.
  • Filters combine with an implicit AND; an -o requires escaped parentheses.
  • -size rounds up to the unit: for small files, reason in bytes with c.
  • ! -name filters the output, -prune really stops the descent into a directory.
  • -exec … {} + groups the files, \; launches one process per file.
  • Always -print before -delete, and never -delete with -prune.

Knowledge check

Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

FAQ: frequently asked questions

The answers below go further than the page, with security audits, performance measurements and production practices.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn