Many commands take arguments but don’t read standard input: rm, chmod, mkdir and docker rm are examples. xargs bridges the gap. It reads items from stdin and runs a command with those items as arguments.
find . -name '*.tmp' | xargs rm
That runs rm ./a.tmp ./b.tmp ./c.tmp …. Where needed, xargs splits a long list into several command runs, so you never hit “Argument list too long”.
Handle spaces and odd filenames: -0
By default xargs splits input on whitespace and treats quotes specially. A file called my file.tmp becomes two arguments, my and file.tmp, which is dangerous with rm. Use NUL-separated input:
find . -name '*.tmp' -print0 | xargs -0 rm
find -print0 and xargs -0 belong together. Other tools that can produce NUL-separated output: grep -lZ, git ls-files -z and sort -z.
For newline-separated input, GNU xargs can split on newlines only:
xargs -d '\n' rm < list.txt
Put the argument somewhere specific: -I
By default, items go at the end of the command. -I {} places each item wherever {} appears, and runs the command once per item:
cat domains.txt | xargs -I {} dig +short {} A
ls *.jpg | xargs -I {} cp {} /backup/{}
find . -name '*.log' -print0 | xargs -0 -I {} mv {} {}.old
Control batch size: -n
echo a b c d e | xargs -n 2 echo
# a b
# c d
# e
-n 1 runs the command once per item. -L 1 runs it once per input line.
Run in parallel: -P
xargs can run several commands at the same time. That’s a simple way to speed up I/O-bound or network-bound jobs:
# compress logs with 4 jobs at once
find /var/log/app -name '*.log' -mtime +1 -print0 | xargs -0 -n 1 -P 4 gzip
# check HTTP status for a list of URLs, 8 at a time
xargs -P 8 -I {} curl -s -o /dev/null -w '%{http_code} {}\n' {} < urls.txt
# ping several hosts
xargs -P 10 -I {} sh -c 'ping -c1 -W1 {} >/dev/null && echo "{} up" || echo "{} down"' < hosts.txt
-P 0 runs as many processes as possible. Choose a sensible number, usually the CPU core count for CPU-heavy work, so you don’t overload the server. For more control (progress, job logs, retries), see GNU parallel.
Preview and confirm
find . -name '*.bak' -print0 | xargs -0 -t rm # -t prints each command before running it
find . -name '*.bak' -print0 | xargs -0 -p rm # -p asks before each run
find . -name '*.bak' -print0 | xargs -0 echo rm # dry run: just print what would run
Putting echo in front of the command is the simplest dry run.
Empty input
If stdin is empty, GNU xargs still runs the command once with no arguments. For rm that’s an error. For other commands it can do something unexpected. Use -r (--no-run-if-empty):
docker ps -aq -f status=exited | xargs -r docker rm
Practical examples
# delete Docker images matching a name
docker images --format '{{.Repository}}:{{.Tag}}' | grep '^myapp:' | xargs -r docker rmi
# search inside files found by find
find /etc -name '*.conf' -print0 | xargs -0 grep -l 'listen 80'
# replace a domain in every file that contains it
grep -rlZ 'old.example.com' /var/www/site | xargs -0 sed -i 's/old.example.com/new.example.com/g'
# kill processes by name (pkill is simpler)
pgrep -f 'node old-server' | xargs -r kill
# install packages listed in a file
xargs -a packages.txt sudo apt install -y
xargs vs find -exec
find . -name '*.tmp' -exec rm {} + # find's own batching — also safe with spaces
find . -name '*.tmp' -delete # simplest for deleting
-exec … {} + batches like xargs and handles any filename safely. Use xargs when you need parallelism (-P), when the input doesn’t come from find, or when you want to combine several tools. See find command.
Quick reference
| Task | Command |
|---|---|
| Safe with spaces | find … -print0 | xargs -0 cmd |
| One run per item | xargs -n 1 cmd |
| Item in the middle | xargs -I {} cmd {} arg |
| Parallel | xargs -P 4 -n 1 cmd |
| Skip if empty | xargs -r cmd |
| Show commands | xargs -t cmd |
| Read from file | xargs -a file cmd |