The xargs Command Explained: Turning Output Into Arguments
Most Linux commands take their input as arguments, not as a stream. rm deletes the files you name on the command line; it does not read a list of filenames from standard input. That mismatch is exactly what xargs solves: it reads items from a pipe and converts them into arguments for another command.
The problem xargs solves
This pipeline looks reasonable but does nothing useful:
find /var/log -name "*.gz" | rm
rm ignores standard input entirely. It deleted nothing and read nothing. The fix:
find /var/log -name "*.gz" | xargs rm
xargs collects the filenames arriving on the pipe and invokes rm file1.gz file2.gz file3.gz ... with as many names as fit into one command line.
Why not just use a loop
You could write a while read loop, but xargs is usually both shorter and dramatically faster:
# Slow: one rm process per file
find . -name "*.tmp" | while read f; do rm "$f"; done
# Fast: a handful of rm processes for thousands of files
find . -name "*.tmp" -print0 | xargs -0 rm
Process startup is expensive. Deleting 10,000 files with one rm invocation per file means 10,000 process launches; xargs might do it in three.
The whitespace trap and -0
By default xargs splits input on any whitespace, so My Document.pdf becomes two broken arguments. The reliable pattern pairs find -print0 with xargs -0, separating items with null bytes instead of newlines:
find ~/Documents -name "*.pdf" -print0 | xargs -0 ls -lh
Null bytes cannot appear in filenames, so this handles spaces, quotes, and even newlines embedded in names. Treat -print0 | xargs -0 as the default form, not the advanced one.
Placing arguments with -I
Normally xargs appends arguments to the end of the command. When you need them elsewhere, -I defines a placeholder:
find . -name "*.bak" -print0 | xargs -0 -I {} mv {} /tmp/backups/
One caveat: -I switches xargs into one-invocation-per-item mode, giving up the batching speed advantage. For mv specifically, mv -t DESTDIR restores batching:
find . -name "*.bak" -print0 | xargs -0 mv -t /tmp/backups/
Controlling batch size and parallelism
Two flags matter once you use xargs seriously. -n caps arguments per invocation, and -P runs invocations in parallel:
# Compress files four at a time, one file per gzip process
find . -name "*.log" -print0 | xargs -0 -n 1 -P 4 gzip
This turns xargs into a lightweight parallel job runner, often removing the need for GNU parallel for simple cases.
Useful everyday patterns
# Count lines in every matching file
find src -name "*.py" -print0 | xargs -0 wc -l
# Search only files that contain a first pattern
grep -rlZ "TODO" src | xargs -0 grep -n "FIXME"
# Kill every process matching a name (pgrep already exists for this, but the pattern generalizes)
pgrep -f myserver | xargs kill
# Preview what WOULD run without running it
find . -name "*.old" | xargs echo rm
The xargs echo trick is worth internalizing: prefixing the target command with echo prints the exact invocations xargs would perform, which is the safest way to check a destructive pipeline before committing to it.
When xargs runs even with no input
A surprise worth knowing: classic xargs runs its command at least once even when input is empty, so a pipeline that finds nothing can still execute rm with no arguments. GNU xargs provides -r (--no-run-if-empty) to skip execution on empty input. On Linux it is a good habit in scripts:
find /tmp/cache -name "*.stale" -print0 | xargs -0 -r rm
xargs vs find -exec
find -exec cmd {} + batches arguments the same way xargs does, entirely inside find. For pure find pipelines the two are equivalent in speed, and -exec avoids the quoting question completely. xargs earns its place when the producer is not find: grep output, a file of package names, command substitution results, or anything else that arrives on a pipe.
Frequently Asked Questions
What does xargs actually do?
xargs reads items from standard input and uses them as arguments to another command. Instead of a command reading a stream, xargs converts the stream into arguments, which is what most commands expect.
Why use xargs instead of a shell loop?
xargs batches many arguments into few command invocations, which is much faster than running the command once per item in a loop. It also handles running commands in parallel with the -P flag.
What is the difference between xargs and find -exec?
find -exec runs the command from within find itself, while xargs receives filenames over a pipe. With -exec cmd {} +, find batches arguments much like xargs does. The xargs approach composes with any producer, not just find.
Why do spaces in filenames break xargs?
By default xargs splits input on any whitespace, so a filename with a space becomes two arguments. Pair find -print0 with xargs -0 so items are separated by null bytes, which cannot appear in filenames.
How do I put the argument in the middle of a command?
Use -I with a placeholder, for example xargs -I {} mv {} /backup/. Note that -I disables batching and runs the command once per input line.
How many arguments does xargs pass at once?
As many as fit within the system argument length limit, which is usually hundreds of kilobytes. You can cap it with -n to pass a fixed number of arguments per invocation.