xargs, one move per page
Twenty-nine moves for building command lines from standard input: how xargs batches items into as few runs as possible, why blank-delimited splitting breaks on spaces in filenames, the null pair find -print0 with xargs -0, the placeholder -I and what it silently implies, limiting arguments with -n, lines with -L, characters with -s, running many at once with -P, delimiters with -d, reading from a file with -a, echoing before running with -t, skipping the empty run with -r, showing the system limits, exit codes 123 to 127, and wrapping commands in sh -c when arguments must come after the names.
A diagram, the command, and one thing to try this week. That's a page.
xargs, one move per page
Twenty-nine moves for building command lines from standard input: how xargs batches items into as few runs as possible, why blank-delimited splitting breaks on spaces in filenames, the null pair find -print0 with xargs -0, the placeholder -I and what it silently implies, limiting arguments with -n, lines with -L, characters with -s, running many at once with -P, delimiters with -d, reading from a file with -a, echoing before running with -t, skipping the empty run with -r, showing the system limits, exit codes 123 to 127, and wrapping commands in sh -c when arguments must come after the names.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
Every fact in this book is as the official sources state it, fetched and read during this build: the GNU findutils manual, Finding Files (gnu.org/software/findutils/manual: Invoking xargs, xargs options, Invoking the shell from xargs, Safe File Name Handling, Limiting Command Size, Controlling Parallelism, Interspersing File Names), the xargs(1) man page text at man7.org, and the find(1) man page for the find-side facts. GNU extensions and POSIX Issue 8 notes are labelled as the sources label them. Demand evidence from live beginner threads, reconfirmed at dispatch; no facts are sourced from Reddit or StackExchange. An independent guide, not affiliated with or endorsed by the Free Software Foundation.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
Ground truth
The mental model that fixes half of everything: xargs reads items from standard input, batches them into as few command runs as it can, and the command defaults to echo. Splitting on blanks is the default and it is a trap.
- 01One run, many items
- 02Splitting on blanks
Per xargs(1): xargs reads items from the standard input, delimited by blanks or newlines, and executes the command (default is echo) one or more times with any initial-arguments followed by items read from standard input; the command line is built up until it reaches a system-defined limit; there will normally be many fewer invocations of the command than there were items, with significant performance benefits.
One run, many items
xargs reads items from standard input, delimited by blanks or newlines, and runs your command with them: initial arguments first, then the items. No command given means the default: echo.
It does not run once per item. It builds a command line until it reaches a system-defined limit, so a thousand items can mean two runs. The manual says it plainly: many fewer invocations than items, significant performance benefits.
Feed it a list, get a handful of full command lines. That is the whole machine.
Run ls | xargs echo this week and watch the listing collapse into one echo line. Then count: one run, many items. Everything else edits the shape of that run.
Per xargs(1): items are delimited by blanks (which can be protected with double or single quotes or a backslash) or newlines; blank lines on the standard input are ignored; because Unix filenames can contain blanks and newlines, this default behaviour is often problematic and filenames containing blanks and/or newlines are incorrectly processed.
Splitting on blanks
The default split is on blanks and newlines: any run of whitespace ends an item. Quotes and backslashes in the input can protect blanks, but nothing in a filename does that for you.
So my file.txt arrives as two items, my and file.txt, and the command runs on things that do not exist. The manual is blunt: Unix filenames can contain blanks and newlines, and the default behaviour incorrectly processes them. Blank input lines are simply ignored.
The default splitter is honest about words, not about names. The next pages fix that.
Create a file with a space in its name, feed find . | xargs ls a try, and watch it break. Keep that failure in mind for the null pair page.
Per xargs(1) and the GNU manual Safe File Name Handling node: -0 makes input items terminated by a null character instead of whitespace, quotes and backslash not special, and the end-of-file string disabled; GNU find -print0 prints the entire file name followed by a null character; both find . -print0 and xargs -0 are POSIX-conforming since Issue 8 (IEEE Std 1003.1-2024); GNU tar, GNU cpio and perl can also process such output.
The null pair, -0
The one true fix: find -print0 piped into xargs -0. The producer ends each name with a null character; the consumer splits on nulls only. A filename can contain a space, a quote, even a newline, but never a null, so nothing can fool the split.
With -0, quotes and backslash stop being special and the end-of-file string is disabled: every character is literal. Both halves are POSIX since Issue 8, 2024, and GNU tar, cpio and perl read the same stream.
Print0 on one side, null on the other. That pair is the habit.
Make the pipe with the space in the name work this week: find . -print0 | xargs -0 ls. Then try to break it with a newline in a filename. You will not.
Per xargs(1): -I replace-str replaces occurrences of replace-str in the initial-arguments with names read from standard input; also, unquoted blanks do not terminate input items, instead the separator is the newline character; implies -x and -L 1; if replace-str is omitted (allowed only for -i) it defaults to {}; the -i option was removed from POSIX; use -I instead.
The placeholder, -I
-I is the pencil: draw the placeholder where the name should go. cp bracket-bracket backup/ puts each name in the slot you chose, not at the end.
It quietly rewrites three rules, exactly as the manual states: newlines become the separator, so blanks inside a name no longer split; it implies -L 1, one line per run, which is why it feels slow; and it implies -x. The old -i form with its default bracket-bracket was dropped from POSIX; -I is the spelling.
Placement freedom, batching cost. -I is precise and it is one-at-a-time.
Rewrite one end-anchored command with -I this week and watch the runs go one by one with -t. Precise, single-file, slower: know what you traded.
Per xargs(1): -P max-procs runs up to max-procs processes at a time; the default is 1; if max-procs is 0, xargs runs as many processes as possible; use the -n or -L option with -P, otherwise chances are that only one exec will be done; while xargs is running, SIGUSR1 increases and SIGUSR2 decreases the number, within an implementation-defined limit shown by --show-limits and never below 1; xargs never terminates its commands and always waits for all children to exit; the called processes must manage shared resources themselves; output from parallel processes is produced in an indeterminate order and very likely mixed up unless they collaborate.
Parallel, -P
-P opens lanes: up to max-procs processes at a time, default 1, and 0 means as many as possible. The slow tree of images the manual describes becomes four cores working at once.
Pair it with -n or -L, or chances are only one exec happens: one fat batch fills one lane. Two cautions: xargs never kills its children and waits for them all, and parallel output is indeterminate and likely mixed unless the commands collaborate or write to separate files.
-P spreads the work. Order and shared resources stay your problem.
Time one batch job this week serial, then with -P 4. Then make each run write its own output file, because the mixed stdout will not read itself.
Per xargs(1) EXAMPLES: cut -d: -f1 < /etc/passwd | sort | xargs echo generates a compact listing of all the users on the system; find /tmp -name core -type f -print0 | xargs -0 /bin/rm -f processes filenames so that names containing spaces or newlines are correctly handled; the -print form works incorrectly if there are filenames containing newlines or spaces.
The pipeline habit
End of the tour, the habit itself: whatever produces a list can end in xargs. The manual's own closing example is cut, sort, xargs echo: every user on the system, one compact line.
The pairing rule you now own: plain lists batch bare; anything from find that touches disk travels the null pair, -print0 into -0, so spaces and newlines never get a vote. The bare -print version, the man page warns, works incorrectly the day a name has a space.
List, batch, run. Choose the delimiter, count the runs, read the exit code.
Build one new habit-line this week: a list you actually make, piped to sort, ending in xargs. Use the null pair if the items are filenames.