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

Related: grep and sed.

  • Linux
  • Command Line
  • Bash