find -exec vs xargs: Choosing the Right Approach
Quick Answer: Which Should You Use?
Default to -exec {} + for simple operations. Choose xargs when you need pipeline integration, parallel execution, or the null-byte safety of -0.
| Situation | Recommended |
|---|---|
| Simple one-command execution | find -exec {} + |
| Filenames with spaces | find -print0 | xargs -0 |
| Parallel execution | xargs -P |
| Integrating into a pipeline | xargs |
| Shell features (variables, conditionals) | find -exec sh -c '...' _ {} \; |
The null-byte safety rule
In any environment where filenames may contain spaces or special characters, always use the -print0 | xargs -0 combination.
How does find -exec work?
find -exec is a built-in feature of find that executes a command directly on each result. No external pipe is needed, making it straightforward for simple use cases.
Semicolon form (\;): One invocation per file
find /var/log -name "*.log" -exec ls -lh {} \;{} is replaced with the matched file path. The backslash prevents the shell from interpreting the semicolon. One command invocation per file — slow when processing large numbers of files.
Plus form (+): Batch execution
find /var/log -name "*.log" -exec ls -lh {} +With +, find buffers the arguments and calls the command as few times as possible, passing multiple files at once. This is significantly faster than \; and behaves similarly to xargs.
-exec {} + is POSIX-compliant and requires no external tools. Arguments are passed directly to the command, so whitespace in filenames never causes splitting issues.
How does xargs work?
xargs converts standard input into command arguments. It is designed to work seamlessly in shell pipelines.
find /var/log -name "*.log" | xargs ls -lh
xargs reads find's output line by line and calls ls -lh file1 file2 file3 ... in batches.
Key options:
-0: Read input delimited by null bytes (use withfind -print0)-I {}: Specify argument placeholder for insertion position-P N: Run N processes in parallel-r: Do not run the command if input is empty-n N: Pass at most N arguments per invocation
Why do filenames with spaces cause problems?
By default, xargs splits input on whitespace. A filename like my file.txt becomes two arguments: my and file.txt.
# Dangerous: breaks on filenames with spaces find . -name "*.txt" | xargs rm # Safe: null-byte delimiter find . -name "*.txt" -print0 | xargs -0 rm
find -print0 outputs filenames terminated by a null byte (\0) instead of a newline. xargs -0 reads null-byte-delimited input. Together they handle any filename regardless of spaces, newlines, or special characters.
When using find -exec {} +, arguments are passed directly to the command without going through a shell pipe — so whitespace in filenames is never a problem.
Filenames in production systems often contain spaces. Get into the habit of using -print0 | xargs -0 whenever you use find | xargs.
Parallel execution with xargs -P
For large-scale file processing, xargs -P can spawn multiple processes simultaneously.
# Convert PNG files to JPEG using 4 parallel processes
find . -name "*.png" -print0 | xargs -0 -P 4 -I {} convert {} {}.jpg-P 4 starts up to 4 processes at once. A reasonable default is the number of CPU cores.
find -exec has no native parallel execution support. When parallelism matters, xargs -P is the right tool.
Using shell features with both commands
Neither find -exec nor xargs can directly execute shell syntax (variable expansion, pipes, redirection). Use sh -c as a wrapper when needed.
# find -exec with shell features (recommended form)
find . -name "*.log" -exec sh -c 'wc -l "$1" >> /tmp/result.txt' _ {} \;
# xargs with shell features
find . -name "*.log" -print0 | xargs -0 -I {} sh -c 'wc -l "{}" >> /tmp/result.txt'Embedding {} directly inside a sh -c string is a shell injection risk if filenames contain shell metacharacters. In the find -exec sh -c '...' _ {} \; form, the filename is passed as $1 instead, which avoids this problem.
Practical patterns
Delete old log files
# Remove .log files older than 30 days
find /var/log -name "*.log" -mtime +30 -exec rm -v {} +Set permissions on files and directories separately
# Directories to 755
find . -type d -exec chmod 755 {} +
# Files to 644
find . -type f -exec chmod 644 {} +Bulk text replacement
# Find files containing the old value, then replace with sed find . -name "*.conf" -print0 | xargs -0 grep -l "old_value" | xargs sed -i 's/old_value/new_value/g'
Remove empty directories
find . -type d -empty -exec rmdir {} +Count lines in all matching files
find . -name "*.py" -print0 | xargs -0 wc -l
Summary
| Dimension | find -exec {} + | xargs |
|---|---|---|
| External tool dependency | None | Requires xargs |
| Whitespace safety | Safe by default | Needs -print0 | -0 |
| Parallel execution | Not supported | Available via -P |
| Pipeline integration | Difficult | Natural |
| Syntax simplicity | Simple | Requires a pipe |
For straightforward operations without spaces in filenames, -exec {} + is the most concise choice. Reach for xargs when you need pipeline integration, parallel processing with -P, or the null-byte safety of -0.