find answers questions no ls can, and xargs turns the answers into actions. The trap is filenames: half the find | xargs one-liners on the internet break the first time a path contains a space.
1. Build a tree worth searching
mkdir -p ~/labs/find/{app/{src,logs},vendor,backup} && cd ~/labs/find
touch app/src/main.py app/src/util.py app/src/"old version.py"
touch app/logs/app.log app/logs/app.log.1 vendor/lib.py
printf 'secret_key=abc123\n' > app/src/config.py
printf 'nothing here\n' > vendor/readme.txt
dd if=/dev/zero of=backup/big.bin bs=1M count=12 status=none
touch -d "40 days ago" app/logs/app.log.1
chmod 777 app/src/util.py
find . -type f | sortNote old version.py — the file with a space. It is there on purpose.
Verify
2. Search by name, type, and depth
find . -name "*.py"
find . -iname "*.PY"
find . -type d
find . -maxdepth 2 -type f
find . -name "*.py" -not -path "./vendor/*"-name is case-sensitive and -iname is not. -not -path is how you exclude a subtree, and it is the flag you will want constantly once a node_modules or vendor directory is involved.
Verify
3. Search by the things that matter in an incident
find . -type f -size +10M
find . -type f -mtime +30
find . -type f -mmin -5
find . -type f -perm -o+w
find . -type f -newer app/src/main.pyIn order: bigger than 10MB, modified more than 30 days ago, modified in the last five minutes, world-writable, and newer than a reference file. The last two are the security-audit pair — -perm -o+w finds files anyone can edit, and -newer answers "what changed since the deploy" when you have a timestamp to compare against.
Verify
Verify
4. Act on the results, and watch the naive form break
find . -name "*.py" | xargs ls -lThat fails on old version.py: xargs splits on whitespace, so it looks for two files that do not exist. The fix is a null separator on both sides:
find . -name "*.py" -print0 | xargs -0 ls -l-print0 emits null-terminated paths and -0 reads them. Null is the only byte a filename cannot contain, which is why this pair is the correct form and the unquoted one is a latent bug.
Verify
5. Prefer -exec when you can
find . -name "*.py" -exec ls -l {} +
find . -name "*.log*" -exec grep -l "" {} \;-exec ... + batches the results into as few invocations as possible, like xargs, and handles spaces natively with no -print0 needed. -exec ... \; runs once per file — slower, but required when the command takes exactly one argument.
Prefer -exec for correctness and reach for xargs when you need its extras: -P for parallelism, -n to batch, -I{} to place the argument mid-command.
find . -name "*.py" -print0 | xargs -0 -P 4 -I{} sh -c 'echo "checked {}"'Verify
6. Dry run before you destroy anything
find . -type f -mtime +30 -name "*.log*"
find . -type f -mtime +30 -name "*.log*" -delete
find . -type f -name "*.log*"Run the search, read the list, then add -delete. Never write the -delete first and trust the predicates — a misplaced -o in a find expression has deleted production data more than once.
-delete also implies -depth, so it removes directory contents before the directory itself. And it refuses to remove non-empty directories, which is a safety net worth knowing you have.
Verify
Clean up
cd ~ && rm -rf ~/labs/findWhere this goes next
You can select exactly the files you mean. Next: reshaping what is inside them.