Skip to content
Linux Administration
Lab 16 of 27·30mBeginner

Find files by anything, then act on them safely

Search a tree by name, age, size, and permission, then run a command over the results without breaking on a space in a filename.

You need

  • A Linux system with a shell

Do first

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 | sort

Note old version.py — the file with a space. It is there on purpose.

Verify

find . -type f | wc -l # 8

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

find . -name "*.py" -not -path "./vendor/*" | wc -l # 4

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.py

In 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

find . -type f -perm -o+w # ./app/src/util.py

Verify

find . -type f -size +10M | wc -l # 1

4. Act on the results, and watch the naive form break

find . -name "*.py" | xargs ls -l

That 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

find . -name "*.py" -print0 | xargs -0 ls -l | wc -l # 5

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

find . -name "*.py" -exec ls {} + | wc -l # 5

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

find . -type f -name "*.log*" | wc -l # 1 — the 40-day-old rotated log is gone, the current one is not

Clean up

cd ~ && rm -rf ~/labs/find

Where this goes next

You can select exactly the files you mean. Next: reshaping what is inside them.