What is xargs — and why rm can't hear your pipe

Try this in a terminal:
echo old.log | rm
Looks like it should delete old.log. Instead you get:
rm: missing operand
rm didn't hear a word. The file name went into the pipe — and hit a wall. Here's the tricky part: every command has two ears, and rm only listens with one. The bridge between them is called xargs.
What xargs is
xargs is a command that takes text from its input and turns it into arguments for another command.
Before: a stream of lines a.txt b.txt c.txt.
After: a call rm a.txt b.txt c.txt.
That's it. The name reads exactly like that: "x args", build the arguments.
Every command has two ears
Any terminal command can get data in two ways.
- Standard input — a stream of text flowing through a pipe
|.grep,sort,wclisten to it. - Arguments — the words after the command name:
rm a.txt.rm,mkdir,cp,touchlisten to those.
A pipe only plugs into the first ear. That's why echo old.log | rm stays silent: rm doesn't read the stream at all. It needs the name on the command line.
xargs pours one ear into the other:
echo old.log | xargs rm
Now the file is gone. xargs read old.log from the pipe and ran rm old.log itself.
The most common real example — delete everything find found:
find . -name "*.tmp" | xargs rm
Three traps everyone trips on
Spaces in names
xargs splits input on spaces, not on lines. To it, my file.txt is two files: my and file.txt. This mess is covered in detail in the article on scripts that break on spaces.
An apostrophe stops everything
We tested on a folder with three files: a.txt, it's.txt and my file.txt.
ls | xargs echo
Output:
xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option
a.txt
xargs treats quotes as special. The apostrophe in it's looks to it like an opening quote with no partner. It handled the first file and silently dropped the rest. Had that been rm, a third of the files would be gone.
Empty input still runs the command
This one is the most surprising. If nothing arrives on input, GNU xargs (Linux) still runs the command — once, with no arguments:
find . -name "*.nope" | xargs ls -l
No files — yet ls -l lists the current folder. With rm you're lucky: it just says missing operand. With another command you might not be. The -r flag (--no-run-if-empty) turns this off. On macOS, xargs already skips the command on empty input.
How to use it safely
One rule fixes spaces and quotes at once: separate names with a null byte.
find . -name "*.tmp" -print0 | xargs -0 rm
-print0 makes find put a \0 between names. -0 makes xargs split only on it. A null byte can't appear in a file name. So neither a space nor an apostrophe can break anything anymore.
Four more flags worth knowing:
-r— don't run the command if input is empty (GNU).-n 1— one argument per run:echo "1 2 3 4" | xargs -n 2 echoprints two lines.-I {}— put the value anywhere:xargs -I {} cp {} backup/.-P 4— run up to four processes in parallel. Handy when you need to compress a hundred images.
Where you'll run into it
- In commands from other people's answers and from your AI assistant.
grep -rl old | xargs sed -i ...is the classic way to replace text across many files. Now you know where the mine is: names with spaces. - Together with
find. When to search withfindand when withgrepis in find vs grep. - When there are too many files.
rm *.jpgon two hundred thousand files fails withArgument list too long: the system caps how long a command line can be. xargs splits the list into batches and callsrmseveral times.
When you see | xargs in an AI answer, check one thing: is there a -0? If you didn't choose the file names yourself, ask it to rewrite with -print0.
How is xargs different from a pipe?
A pipe passes text to standard input. xargs reads that text and turns it into arguments for the next command. They work together: find | xargs rm.
Can I do without xargs?
Often, yes. find has -exec rm {} + and -delete, which don't get confused by spaces. But xargs is more universal: it works with the output of any command, not just find.
Is there xargs on Windows?
Not in PowerShell. It's there in Git Bash and WSL. In PowerShell itself you do the same with ForEach-Object.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





