Finding Files with find, locate, and xargs
Finding the right filesystem objects is more than matching a name. DevOps investigations combine roots, type, ownership, size, age, and actions—and they must remain correct when filenames contain spaces, newlines, or leading dashes.
Learning objectives
By the end of this lesson
-
Explain the different data sources used by
findandlocate. -
Build
findexpressions from roots, tests, operators, and actions. - Select files by name, type, size, time, and path while controlling traversal.
-
Use
-execand NUL-delimitedxargssafely. - Perform a dry-run-first cleanup analysis without deleting data.
1. Three tools solve different parts of the problem
flowchart TB N["Need filesystem objects"] --> F["find scans a live directory hierarchy"] N --> L["locate queries a previously built filename database"] F --> X["xargs groups input into command arguments"] L --> X F --> E["-exec runs an action directly"]
find walks directory entries at query time, so its
result reflects the current tree and can test metadata.
locate searches an index created by
updatedb, so it is fast but may be stale or omit paths
according to database policy. xargs reads items from
standard input and constructs one or more command invocations.
Newline-delimited output is not a universal filename transport.
Linux filenames may contain spaces, tabs, quotes, and newlines.
Use NUL-delimited pairs such as
find -print0 | xargs -0 when passing arbitrary
pathnames.
2. Read a find command as an expression
The general model is find ROOT... EXPRESSION. Roots
define where traversal begins. Tests return true or false. Operators
combine tests. Actions print, execute, or otherwise process selected
entries.
root="$HOME/devops-academy/linux/chapter05/lesson03/tree"
# Regular log files beneath the root, matched case-sensitively.
find "$root" -type f -name '*.log' -print
# Configuration files or YAML files; parentheses must be protected from the shell.
find "$root" -type f \( -name '*.conf' -o -name '*.yaml' \) -print
# Negate an archive subtree by path.
find "$root" -type f ! -path '*/archive/*' -name '*.log' -print
Adjacent tests imply logical AND. Use \( and
\) to group expressions because unquoted parentheses
are shell syntax. Quote wildcard patterns so find, not
the shell, matches them against each visited pathname.
3. Select by type, size, and time
-type fRegular files-type f
-name PATTERNBasename pattern-name '*.log'
-path PATTERNWhole pathname pattern-path '*/cache/*'
-size +100MFiles larger than 100 MiB units-size +100M
-mtime +7Modification age in rounded 24-hour periods-mtime +7
-newermt DATEGNU date-based modification comparison-newermt '2026-08-01'
-emptyEmpty regular files or directories-empty
# Large regular files with human-readable metadata.
find "$root" -type f -size +1M -printf '%s bytes\t%TY-%Tm-%Td %TH:%TM\t%p\n'
# Files modified during a bounded window (GNU find).
find "$root" -type f \
-newermt '2026-08-04 09:00:00' \
! -newermt '2026-08-04 11:00:00' \
-print
Time tests have precise rounding rules. When an incident depends on
exact boundaries, prefer explicit timestamps, inspect with
stat, and record the timezone used by the
investigation.
4. Bound traversal and handle errors
Start from the narrowest practical root. -maxdepth and
-mindepth constrain levels in GNU find.
-xdev avoids descending onto other filesystems.
-prune prevents traversal into selected directories.
# Search a deployment tree but skip .git, cache, and vendor directories.
find "$root" \
\( -type d \( -name .git -o -name cache -o -name vendor \) -prune \) \
-o \( -type f -name '*.yaml' -print \)
# Stay on the same filesystem and limit depth.
find "$root" -xdev -maxdepth 3 -type f -print
Permission-denied diagnostics are evidence. Do not automatically
hide standard error with 2>/dev/null; doing so can
make an incomplete result look complete. If noise must be separated,
capture it in a file and report that the traversal encountered
inaccessible paths.
5. Execute actions without corrupting filenames
-exec COMMAND {} \; runs one command per selected
entry. -exec COMMAND {} + groups multiple pathnames
into fewer invocations. The grouped form is usually more efficient
and retains direct pathname handling.
# One stat process per group of selected files.
find "$root" -type f -name '*.log' -exec stat --format='%s %n' -- {} +
# NUL-delimited transport to xargs for arbitrary names.
find "$root" -type f -name '*.log' -print0 \
| xargs -0 -r grep -Hn -F 'level=ERROR' --
# Preview grouped arguments rather than changing files.
find "$root" -type f -name '*.tmp' -print0 \
| xargs -0 -r printf 'candidate: %s\n'
GNU xargs -r avoids running the command when input is
empty; it is not universally portable. For destructive work, prefer
a preview, an explicit root, NUL delimiters, and an action that
cannot escape the intended tree.
6. Use locate for discovery, then verify live state
locate PATTERN searches a filename database, often
updated periodically. It is excellent for quickly discovering likely
locations, but the result may include deleted files or miss newly
created ones.
# Find likely configuration filenames quickly.
locate '/nginx.conf'
locate -i '*prometheus*.yml'
# Verify each candidate against the live filesystem.
locate -0 '*deployment*.yaml' \
| while IFS= read -r -d '' path; do
if [[ -f $path ]]; then
printf 'live file: %s\n' "$path"
fi
done
Database coverage depends on how updatedb is configured
and which filesystems or paths it excludes. Treat locate as an
index-assisted lead, not authoritative inventory.
7. Hands-on lab: build a cleanup candidate report
lab="$HOME/devops-academy/linux/chapter05/lesson03"
root="$lab/tree"
rm -rf -- "$root"
mkdir -p -- "$root"/{active,archive,cache,"odd names"}
printf 'level=INFO\n' > "$root/active/app.log"
printf 'level=ERROR\n' > "$root/active/error log.log"
dd if=/dev/zero of="$root/cache/blob.tmp" bs=1024 count=8 status=none
: > "$root/cache/empty.tmp"
printf 'old archive\n' > "$root/archive/old.log"
touch -d '10 days ago' -- "$root/archive/old.log" 2>/dev/null || true
report="$lab/candidates.txt"
find "$root" -xdev -type f \
\( -name '*.tmp' -o -path '*/archive/*' \) \
-printf '%TY-%Tm-%Td %TH:%TM\t%s\t%p\n' \
| sort > "$report"
cat -- "$report"
printf '%s\n' '=== ERROR records in every log ==='
find "$root" -type f -name '*.log' -print0 \
| xargs -0 -r grep -Hn -F 'level=ERROR' --
Verification checklist
8. Safe deletion is a separate operation
Do not append -delete while designing a query. First
save and inspect the exact candidates. Then rerun the same bounded
predicate with a controlled action, ideally after a snapshot or
backup. Remember that actions can affect expression truth and
traversal order.
Use a disposable lab, print candidates, count them, inspect unusual names, and keep the root in a variable you verify before any removal command.
9. Knowledge check
Question 1. Why can locate return a
pathname that no longer exists?
Question 2. Why should '*.log' be
quoted in a find expression?
Question 3. What problem does
-print0 | xargs -0 solve?
10. Summary
find performs current metadata-aware traversal,
locate provides fast indexed discovery, and
xargs groups streamed items into command arguments.
Safe workflows bound the root, quote patterns, preserve errors, use
NUL delimiters, preview candidates, and separate selection from
destructive action.
11. Further reading
-
GNU Findutils manual —
find,locate,updatedb, andxargs. -
GNU Coreutils manual —
stat,sort, and NUL-aware tools. -
GNU Bash Reference Manual — quoting, pipelines, and
read -d. -
Linux manual pages for
find,xargs,locate, andupdatedb. - Filesystem Hierarchy Standard — common search roots and directory purposes.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.