ZetCode

Linux xargs Command

last modified October 23, 2026

The xargs command in Linux reads items from standard input and runs a command with those items as arguments. It bridges commands that produce lists, such as find, grep -l, or ls, with commands that expect arguments, such as rm, wc, or gzip. Many commands do not read file names from standard input, so a plain pipe is not enough; this is where xargs comes in.

The syntax is xargs [options] [command [initial-arguments]]. xargs collects as many input items as fit on a command line, runs the command, and repeats until the input is exhausted. It can also run commands in parallel. The examples use GNU xargs from findutils 4.9.

Table of contents

Basic usage

xargs splits its input into items at blanks and newlines and appends them to the command. Without a command, it uses echo.

echo one two three | xargs
seq 5 | xargs
seq 5 | xargs echo numbers:

The five lines produced by seq become five arguments of a single echo, so they are printed on one line. Arguments given after the command, such as numbers:, come before the input items.

$ echo one two three | xargs
one two three
$ seq 5 | xargs
1 2 3 4 5
$ seq 5 | xargs echo numbers:
numbers: 1 2 3 4 5

Passing file lists

The typical use is turning a list of file names into arguments.

ls logs/*.log | xargs wc -l

wc receives the three file names as arguments, exactly as if we had typed them. Since wc is run only once, it also prints a total.

$ ls logs/*.log | xargs wc -l
 100 logs/app1.log
 200 logs/app2.log
 300 logs/app3.log
 600 total

Arguments per command

The -n option sets the maximum number of items per command, and -L the number of input lines per command.

seq 6 | xargs -n 2
seq 6 | xargs -n 4 echo
printf "a b\nc\nd e f\n" | xargs -L 1 echo

With -n 2, echo runs three times. The last group may be smaller. -L 1 keeps the items of each line together, which is useful for input where a line represents one record.

$ seq 6 | xargs -n 2
1 2
3 4
5 6
$ seq 6 | xargs -n 4 echo
1 2 3 4
5 6
$ printf "a b\nc\nd e f\n" | xargs -L 1 echo
a b
c
d e f

Tracing commands

The -t option prints each command to standard error before running it.

seq 4 | xargs -t -n 2 echo

Tracing shows how xargs groups the input, which helps when building more complex commands. A safe way to test a destructive command is to put echo in front of it, as in xargs echo rm.

$ seq 4 | xargs -t -n 2 echo
echo 1 2
1 2
echo 3 4
3 4

File names with spaces

Because xargs splits at blanks, file names containing spaces are broken into pieces. The -0 option uses NUL characters as separators instead.

find docs -type f | xargs wc -l
find docs -type f -print0 | xargs -0 wc -l
find docs -type f | xargs -d "\n" wc -l

In the first command, meeting notes.txt becomes two arguments and wc fails. find -print0 together with xargs -0 is the robust solution, since file names cannot contain NUL. Other commands produce NUL-separated output as well, such as grep -lZ and sort -z. Splitting only at newlines with -d "\n" handles spaces too, but not the rare names that contain newlines.

$ find docs -type f | xargs wc -l
wc: docs/meeting: No such file or directory
wc: notes.txt: No such file or directory
1 docs/plan.txt
1 total
$ find docs -type f -print0 | xargs -0 wc -l
 1 docs/meeting notes.txt
 1 docs/plan.txt
 2 total
$ find docs -type f | xargs -d "\n" wc -l
 1 docs/meeting notes.txt
 1 docs/plan.txt
 2 total

Quotes and backslashes

In its default mode, xargs interprets single quotes, double quotes, and backslashes in the input, similar to a shell.

echo "it's here" | xargs echo
echo "\"quoted words\" plain" | xargs -n 1 echo
echo "a\\b" | xargs echo

An apostrophe in a file name or text starts a quoted string, and xargs stops with an error when the quote is not closed. Quoted words form one item, and backslashes are removed. This behaviour is another reason to use -0 or -d, which turn off all quote processing.

$ echo "it's here" | xargs echo
xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option
$ echo "\"quoted words\" plain" | xargs -n 1 echo
quoted words
plain
$ echo "a\\b" | xargs echo
ab

Custom delimiters

The -d option sets a custom item delimiter.

echo "red,green,blue" | xargs -d , -n 1 echo
printf "red,green,blue" | xargs -d , -n 1 echo

With a custom delimiter, the newline is an ordinary character. The output of echo ends with a newline, so the last item is blue followed by a newline, which shows up as an empty line. printf without \n avoids the problem.

$ echo "red,green,blue" | xargs -d , -n 1 echo
red
green
blue

$ printf "red,green,blue" | xargs -d , -n 1 echo
red
green
blue

Replace strings

The -I option defines a placeholder that is replaced with the input item. It allows placing the item anywhere in the command, even several times.

ls logs/*.log | xargs -I {} cp {} backup/
cat names.txt | xargs -I NAME echo "Hello, NAME!"

-I runs the command once per input line, and the whole line is the item, so names with spaces are preserved. The placeholder can be any string; {} is a convention. For copying into a directory, xargs cp -t backup/ is more efficient, since it copies all files in one call.

$ mkdir backup
$ ls logs/*.log | xargs -I {} cp {} backup/
$ ls backup
app1.log
app2.log
app3.log
$ cat names.txt | xargs -I NAME echo "Hello, NAME!"
Hello, alpha!
Hello, beta!
Hello, gamma!
Hello, delta!
Hello, epsilon!

Running shell commands safely

To run several commands or use shell features for each item, xargs starts a shell with sh -c. The item must be passed as an argument of the shell, not inserted into the script text.

ls x* | xargs -I {} sh -c 'echo processing {}'
ls x* | xargs -I {} sh -c 'echo processing "$1"' sh {}
seq 3 | xargs sh -c 'for f; do echo "item $f"; done' sh

We create a file named x; echo INJECTED. In the first command, the name becomes part of the shell script, so the shell executes the injected command. This is a security hole when file names come from untrusted sources. In the second command, the name is passed as the positional parameter $1 and is treated as data. The word sh after the script fills $0. The last form processes many items in one shell by looping over the positional parameters.

$ touch "x; echo INJECTED"
$ ls x* | xargs -I {} sh -c 'echo processing {}'
processing x
INJECTED
$ ls x* | xargs -I {} sh -c 'echo processing "$1"' sh {}
processing x; echo INJECTED
$ seq 3 | xargs sh -c 'for f; do echo "item $f"; done' sh
item 1
item 2
item 3

Empty input

By default, GNU xargs runs the command once even if the input is empty. The -r option prevents this.

find . -name "*.tmp" | xargs ls
find . -name "*.tmp" | xargs -r ls

No .tmp file exists, so ls runs without arguments and lists the current directory. With a command like rm or chmod, running without arguments can do something unexpected, so -r (--no-run-if-empty) belongs in most scripts.

$ find . -name "*.tmp" | xargs ls
backup
docs
logs
names.txt
urls.txt
$ find . -name "*.tmp" | xargs -r ls

Asking for confirmation

The -p option prints each command and asks for confirmation before running it.

echo backup/app1.log | xargs -p rm

We answered y, and the file was removed. Any answer not starting with y or Y skips the command. The prompt reads from the terminal, so it works even though standard input is the pipe.

$ echo backup/app1.log | xargs -p rm
rm backup/app1.log?...y
$ ls backup
app2.log
app3.log

Parallel execution

The -P option runs up to the given number of commands at the same time.

start=$SECONDS; seq 4 | xargs -n 1 sleep; echo "sequential: $((SECONDS - start)) s"
start=$SECONDS; seq 4 | xargs -n 1 -P 4 sleep; echo "parallel: $((SECONDS - start)) s"
printf "%s\n" 3 1 2 | xargs -n 1 -P 3 sh -c 'sleep "$1"; echo "finished $1"' sh

Sequentially, the four sleeps take 1 + 2 + 3 + 4 = 10 seconds; in parallel, they take as long as the longest one. We measure the time with the Bash SECONDS variable. Each command needs its own items, so -P is combined with -n or -I. The output of parallel commands appears in the order they finish, not in the input order, and lines from different commands can interleave. -P 0 runs as many processes as possible; the nproc command prints the number of CPUs as a sensible value.

$ start=$SECONDS; seq 4 | xargs -n 1 sleep; echo "sequential: $((SECONDS - start)) s"
sequential: 10 s
$ start=$SECONDS; seq 4 | xargs -n 1 -P 4 sleep; echo "parallel: $((SECONDS - start)) s"
parallel: 4 s
$ printf "%s\n" 3 1 2 | xargs -n 1 -P 3 sh -c 'sleep "$1"; echo "finished $1"' sh
finished 1
finished 2
finished 3

Process slots

The --process-slot-var option sets an environment variable to the number of the slot each parallel command runs in.

seq 1 6 | xargs -n 1 -P 2 --process-slot-var=SLOT sh -c 'echo "slot $SLOT got $1"; sleep 0.2' sh | sort

With two parallel processes, the slots are 0 and 1. A slot number can be used to give each worker its own resource, such as an output file or a database connection.

$ seq 1 6 | xargs -n 1 -P 2 --process-slot-var=SLOT sh -c 'echo "slot $SLOT got $1"; sleep 0.2' sh | sort
slot 0 got 1
slot 0 got 3
slot 0 got 5
slot 1 got 2
slot 1 got 4
slot 1 got 6

Command line limits

The kernel limits the total size of arguments and environment of a new process. xargs respects this limit by splitting the input into several invocations.

xargs --show-limits < /dev/null 2>&1 | head -n 4
getconf ARG_MAX
seq 1 200000 | xargs echo | wc -l
seq 1 20 | xargs -s 30 echo

The limits are printed to standard error, so we redirect it to see the first lines. On this system, a command line can be about 2 MB long. Two hundred thousand numbers do not fit in one command, so echo runs ten times. The -s option sets a smaller maximum command line size in bytes, which we use to make the splitting visible.

$ xargs --show-limits < /dev/null 2>&1 | head -n 4
Your environment variables take up 353 bytes
POSIX upper limit on argument length (this system): 2094751
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2094398
$ getconf ARG_MAX
2097152
$ seq 1 200000 | xargs echo | wc -l
10
$ seq 1 20 | xargs -s 30 echo
1 2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19
20

Argument list too long

When a shell expansion produces more arguments than the limit allows, the command cannot start. xargs avoids the error.

/bin/echo $(seq 1 1000000) | wc -c
seq 1 1000000 | xargs /bin/echo | wc -c

The same happens with wildcards in directories with very many files, as in rm *.log. Piping the names through xargs, or using find -exec ... +, splits them into acceptable batches.

$ /bin/echo $(seq 1 1000000) | wc -c
bash: /bin/echo: Argument list too long
0
$ seq 1 1000000 | xargs /bin/echo | wc -c
6888896

Exit status

The exit status of xargs summarizes the commands it ran.

printf "%s\n" logs/app1.log missing.log | xargs ls > /dev/null; echo "exit: $?"
echo x | xargs nosuchcommand; echo "exit: $?"
seq 3 | xargs -n 1 sh -c 'echo "run $1"; [ "$1" -ne 2 ] || exit 255' sh; echo "exit: $?"

Status 123 means that at least one invocation failed with a status from 1 to 125. Status 127 means the command was not found. When a command exits with 255, xargs stops immediately and returns 124; the third item is never processed.

$ printf "%s\n" logs/app1.log missing.log | xargs ls > /dev/null; echo "exit: $?"
ls: cannot access 'missing.log': No such file or directory
exit: 123
$ echo x | xargs nosuchcommand; echo "exit: $?"
xargs: nosuchcommand: No such file or directory
exit: 127
$ seq 3 | xargs -n 1 sh -c 'echo "run $1"; [ "$1" -ne 2 ] || exit 255' sh; echo "exit: $?"
run 1
run 2
xargs: sh: exited with status 255; aborting
exit: 124

Reading items from a file

The -a option reads the items from a file instead of standard input.

xargs -a names.txt -n 2 echo
xargs -a urls.txt -n 1 -P 3 curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" | sort

Keeping standard input free matters for commands that read from it. The second command checks three URLs in parallel with curl and prints their status codes. For interactive programs such as editors, the -o option reopens standard input as the terminal.

$ xargs -a names.txt -n 2 echo
alpha beta
gamma delta
epsilon
$ xargs -a urls.txt -n 1 -P 3 curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" | sort
200 https://httpbin.org/robots.txt
200 https://httpbin.org/user-agent
200 https://httpbin.org/uuid

Practical recipes

Some common combinations.

grep -l "^1$" logs/*.log | xargs -r sed -i 's/^1$/one/'
find logs -name "*.log" -print0 | xargs -0 -P 2 -n 1 gzip
pgrep -f "sleep 30[01]" | xargs -r kill

The first command edits only the files that contain a match. The second compresses log files in parallel. The third terminates processes found by pgrep; -r prevents running kill without arguments when nothing matches.

$ grep -l "^1$" logs/*.log | xargs -r sed -i 's/^1$/one/'
$ head -n 1 logs/*.log
==> logs/app1.log <==
one

==> logs/app2.log <==
one

==> logs/app3.log <==
one
$ find logs -name "*.log" -print0 | xargs -0 -P 2 -n 1 gzip
$ ls logs
app1.log.gz
app2.log.gz
app3.log.gz
$ sleep 300 & sleep 301 &
$ pgrep -f "sleep 30[01]" | xargs -r kill
$ pgrep -fc "sleep 30[01]"
0

xargs versus find -exec

For files found by find, -exec command {} + batches the arguments just like xargs and handles any file name safely, without the need for -print0. xargs is the better choice when the list comes from another command, when commands should run in parallel with -P, or when the number of items per command must be controlled. See the find command tutorial for -exec.

Best practices for xargs

Source

GNU xargs manual

In this article, we have used the xargs command to build command lines from input, handle spaces, quotes, and delimiters, place arguments with replace strings, run shell commands safely, run commands in parallel, deal with command line limits, and interpret its exit status.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.