The hard part of find -exec is not finding files; it is making sure the action runs on the right matches, handles odd filenames, and does not touch anything destructive before you review the target list.
Use find -exec on Linux when search results should feed directly into tools such as ls, cat, cp, gzip, chmod, grep, mv, or a small shell wrapper. The key choice is between single-file execution with \;, batched execution with +, and safer review patterns such as -print or -ok before any command changes files.
Understand the find -exec Command
What find -exec Does
Plain find answers which paths match your search. The -exec action answers what to do with those paths, so the selection logic and the file operation stay in one expression. That is often easier to reason about than a pipeline when filenames contain spaces, newlines, quotes, or other characters that break careless shell loops.
Verify GNU find Is Available
Most full Linux installations already include GNU find from the findutils package. Confirm the version before relying on GNU-specific behavior such as -printf or -execdir:
find --version
Relevant output starts with:
find (GNU findutils) 4.10.0
If the shell prints find: command not found, install the findutils package for your distro family:
sudo apt install findutils # Debian, Ubuntu, Linux Mint
sudo dnf install findutils # Fedora, RHEL, Rocky Linux, AlmaLinux
sudo pacman -S findutils # Arch Linux, Manjaro
sudo zypper install findutils # openSUSE
apk add findutils # Alpine, run as root when you need GNU find
Minimal containers and rescue images may expose BusyBox find instead. BusyBox supports basic -exec ... {} \;, -exec ... {} +, and -ok forms, but it does not support GNU -printf and may omit -execdir.
Basic find -exec Syntax
The two core forms differ only in the command terminator:
find [path] [expression] -exec [command] {} \;
find [path] [expression] -exec [command] {} +
- [path]: Where to start searching. Use
/var/logto scan log directories,.for the current directory, or~for your home folder. - [expression]: What to search for. Examples:
-name "*.txt"finds text files,-mtime +30finds files older than 30 days,-size +100Mfinds files larger than 100 megabytes. - -exec [command]: The action to perform on each match. Use
ls -lhto list details,cpto copy,gzipto compress, or any command you would normally run manually. - {}: Placeholder replaced by the current matched path. If you search an absolute path,
{}is absolute; if you search., it is usually relative. - \;: Marks the end of the -exec command. The backslash escapes the semicolon so your shell does not interpret it prematurely.
- +: Alternative terminator that batches multiple files into a single command invocation. GNU find requires
{}immediately before+, so-exec ls -lh {} +works, while-exec cp {} /backup +does not.
The GNU findutils manual for batched -exec documents the {} + form, while the single-file form remains the most portable pattern for commands that need one path at a time.
Choose find -exec, -ok, xargs, or a Shell Wrapper
Pick the form based on how the command expects filenames and how much review you want before it runs:
| Need | Pattern | Use When |
|---|---|---|
| Run once per file | -exec command {} \; | The command modifies one file, prompts per file, or needs the filename in a specific position. |
| Batch many files | -exec command {} + | The command accepts many path arguments at the end, such as ls, grep, or chmod. |
| Run multiple shell commands | -exec sh -c '...' _ {} \; | You need shell features such as variables, command substitution, loops, or more than one command. |
| Prompt before each action | -ok command {} \; | A deletion, move, or permission change needs manual confirmation for each match. |
| Use parallel processing | find ... -print0 | xargs -0 -P N command | You need xargs features such as -P for parallel jobs. Use null-delimited input with -print0 and -0. |
Understanding -exec vs -execdir
GNU -execdir works like -exec but runs the command from the directory containing the matched file, not from the directory where you launched find. This matters for commands that rely on relative paths and for operations where processing each file beside its neighbors is safer.
With -exec, the {} placeholder keeps the matched path exactly as find reports it, such as /var/log/app/error.log from an absolute search. With -execdir, find changes to /var/log/app/ first, then passes ./error.log. That shorter path avoids some path-based surprises, especially when the action creates or moves files relative to the match.
Use -execdir when:
- Moving or renaming files to locations relative to their current directory
- Running commands that expect to operate in the same directory as the file
- Processing untrusted filenames where path injection is a concern
List Files with find -exec ls
At its simplest, find -exec runs a command on every match. List detailed information for every PDF under your Documents directory:
find ~/Documents -name "*.pdf" -exec ls -lh {} \;
The output shows one row per PDF with the permissions, owner, size, timestamp, and path:
-rw-r--r-- 1 user user 2.4M Jan 15 10:30 /home/user/Documents/report.pdf -rw-r--r-- 1 user user 156K Jan 12 08:15 /home/user/Documents/notes.pdf
Common find -exec Patterns by Task
Use these patterns as starting points, then adjust the path and expression to match your files:
| Task | Command Pattern | What It Does |
|---|---|---|
| Show file details | find . -name "*.conf" -exec ls -lh {} + | Batches config-file matches into fewer ls calls. |
| Print file contents | find . -name "*.txt" -exec cat {} \; | Prints each matching text file in turn. |
| Back up files | find . -name "*.jpg" -exec cp {} {}.backup \; | Creates a same-directory .backup copy for each image. |
| Compress files safely | find . -name "*.log" -exec gzip -k {} \; | Creates .gz copies while keeping the original logs. |
| Change permissions | find . -type f -name "*.sh" -exec chmod +x {} + | Makes matching shell scripts executable. Pair bulk permission work with the chmod command guide when the mode is not obvious. |
| Search file contents | find . -name "*.cfg" -exec grep -H "error" {} + | Searches matching config files while preserving filenames in output. For pattern options, use the grep command guide. |
| Move beside match | find . -type f -name "*.tmp" -execdir sh -c 'mkdir -p ./archive && mv -- "$1" ./archive/' _ {} \; | Creates an archive directory next to each match, then moves the file there. |
| Preview deletion | find . -name "*.log" -mtime +30 -print | Shows the exact old logs before you replace -print with a deletion action. |
Use \; when you need one command execution per file, which is safer for operations that modify files. Use + when processing many files with read-only commands like ls or grep to reduce process spawning overhead.
Deletion, moves, overwrites, and recursive permission changes can affect many files at once. Preview matches with
-okwhen you want per-file confirmation, and keep backups when the operation cannot be reversed.
Practical find -exec Command Examples
Back Up Files with find -exec cp
Before a batch edit, create same-directory backup copies of the files you plan to change. This pattern keeps the original filename visible and adds a .backup suffix to each copy:
find ~/Pictures -type f -name "*.jpg" -exec cp -p {} {}.backup \;
The -p option preserves file mode, ownership when possible, and timestamps. Use a separate backup directory when you do not want backup files mixed into the source tree.
Print Matching Files with find -exec cat
Use cat with -exec when the matching files are small enough to read directly in the terminal:
find ~/Documents -type f -name "*.txt" -exec cat {} \;
Typical output is the concatenated content of each matching file:
Project notes Meeting follow-up Release checklist
For large text files, replace cat with grep, head, or less so you do not flood the terminal.
Rename Extensions with find -exec and mv
Bulk renaming is safer when you preview the old and new names before moving anything. The shell expansion ${1%.html}.htm removes the final .html suffix and appends .htm. For broader move and rename patterns, use the mv command guide.
find ~/web -type f -name "*.html" -exec sh -c 'printf "%s -> %s\n" "$1" "${1%.html}.htm"' _ {} \;
Renaming can overwrite files when the destination already exists. The move step uses
mv -nto avoid clobbering existing.htmfiles.
find ~/web -type f -name "*.html" -exec sh -c 'mv -n -- "$1" "${1%.html}.htm"' _ {} \;
Compress Logs with find -exec gzip
For old logs, preview the matches first, then compress them with gzip -k. The -k flag keeps the original file while creating a .gz copy:
sudo find /var/log -type f -name "*.log" -mtime +7 -print
sudo find /var/log -type f -name "*.log" -mtime +7 -exec gzip -k {} \;
The -mtime +7 test matches files whose content was last modified more than 7 days ago. Use a larger number, such as +30, when recent logs need to remain uncompressed for active troubleshooting.
Remove Empty Directories Safely with find
Empty directories are a good place to use -delete instead of -exec rmdir because GNU find handles depth-first ordering automatically. Preview the directories before deletion:
find /data -depth -type d -empty -print
-deletepermanently removes matches. Check the preview output carefully before running the deletion command.
find /data -depth -type d -empty -delete
The explicit -exec version also works when you add -depth, which processes children before parents:
find /data -depth -type d -empty -exec rmdir {} \;
For more directory removal behavior, see the rmdir command guide.
Advanced find -exec Techniques
Run Multiple Commands with find -exec sh
Use sh -c when one matched file needs more than a single command. With the batched {} + form, pass matches as positional parameters and loop over them with for file do ... done:
find ~/Documents -type f -name "*.pdf" -exec sh -c '
for file do
printf "File: %s\n" "$file"
ls -lh -- "$file"
done
' _ {} +
The underscore becomes $0 inside the shell. Each matched path becomes $1, $2, and so on, so the loop can handle filenames with spaces without reparsing command output.
Sync Matching Files with find and rsync
When a command reads a list of files from standard input, -printf can be a better fit than -exec. This GNU find pattern sends a null-terminated PDF list to rsync while preserving paths relative to /local/docs:
find /local/docs -type f -name "*.pdf" -printf '%P\0' | rsync -av --from0 --files-from=- /local/docs/ user@example.com:/remote/docs/
-printf '%P\0' emits each path relative to the search root and separates names with null bytes. rsync --from0 --files-from=- reads that list safely, including filenames with spaces or newlines.
Date Stamp Files with basename in find -exec
basename and dirname help rebuild filenames while keeping each file in its original directory. Preview the rename plan first:
find /data/reports -type f -name "*.csv" -exec sh -c 'printf "%s -> %s\n" "$1" "$(dirname "$1")/$(date +%Y%m%d)-$(basename "$1")"' _ {} \;
Date-stamping changes filenames and can collide with an existing dated file. The move command uses
mv -nso an existing destination is left untouched.
find /data/reports -type f -name "*.csv" -exec sh -c 'mv -n -- "$1" "$(dirname "$1")/$(date +%Y%m%d)-$(basename "$1")"' _ {} \;
Generate Large File Reports with find -exec
For capacity checks, batch matching files into one ls run and save the report to a file. For deeper disk usage analysis, pair this with the du command guide.
find /home -type f -size +100M -exec ls -lh {} + > /tmp/large-files-report.txt
The report keeps one row per large file. If /home contains directories you cannot read, run the search from a narrower path you own or use the permission troubleshooting pattern later.
Build Directories from File Names with find -exec
Media workflows often need a directory named after each source file. basename "$1" .mp4 strips the extension safely, even when the filename contains spaces:
find /videos -type f -name "*.mp4" -exec sh -c 'mkdir -p "/archive/$(basename "$1" .mp4)"' _ {} \;
This creates directories such as /archive/Training Clip from /videos/Training Clip.mp4. For nested directory creation options, use the mkdir command guide.
Troubleshoot Common find -exec Errors
find: command not found
A minimal image or stripped-down container may not include find. Confirm the command lookup first:
command -v find
A working install prints a path similar to this:
/usr/bin/find
If there is no output, install findutils with the package command from the availability section, then rerun find --version.
Missing Argument to -exec
If you see this error:
find: missing argument to `-exec'
-exec did not receive a valid terminator. End the action with escaped \; for one file at a time, or put {} immediately before + for batching:
find . -name "*.txt" -exec ls -la {} \;
find . -name "*.txt" -exec ls -la {} +
Single quotes around the semicolon also work: -exec ls -la {} ';'. Do not write -exec cp {} /backup +; batching only works when {} appears right before +.
Unknown Predicate --exec
If you use two dashes, GNU find treats the option as an unknown predicate:
find: unknown predicate `--exec'
Use the single-dash action name instead:
find . -type f -name "*.log" -exec ls -lh {} \;
Permission Denied Errors
When find enters directories your user cannot read, it reports permission errors:
find: '/root': Permission denied find: '/var/lib/private': Permission denied
Search a narrower path when possible. If you only want to hide permission noise while continuing through readable paths, redirect standard error:
find / -name "*.conf" -exec ls -la {} \; 2>/dev/null
Use sudo only when you truly need to inspect protected directories.
Files with Spaces or Special Characters
Filenames containing spaces, quotes, or shell metacharacters break when a shell wrapper expands an unquoted variable. Keep the filename in a positional parameter and quote it:
# Wrong: breaks on filenames with spaces
find . -name "*.txt" -exec sh -c 'cat $1' _ {} \;
# Correct: quotes protect the filename
find . -name "*.txt" -exec sh -c 'cat "$1"' _ {} \;
The underscore before {} becomes $0 inside the subshell, so the matched filename starts at $1.
rmdir Fails on Non-Empty Directories
If directory cleanup prints this error:
rmdir: failed to remove '/path/dir': Directory not empty
The directory was not empty when rmdir ran, or a parent directory was processed before its children. Add -depth so children are handled first:
find /data -depth -type d -empty -exec rmdir {} \;
If you do not need the explicit rmdir call, find /data -depth -type d -empty -delete is shorter after you preview the matches.
-printf Not Recognized
BusyBox find does not support GNU -printf. The error begins like this:
find: unrecognized: -printf
Install GNU findutils when you need -printf, or use a simpler command when only the basename matters:
find . -name "*.pdf" -exec basename {} \;
Conclusion
find -exec is most useful when the search and the action need to stay together. Use \; for one-path operations, {} + for batched read-only or multi-argument commands, and sh -c only when shell features are needed. Preview destructive matches first, then choose -ok, backups, or no-clobber flags when mistakes would be expensive.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>