When you need to run a command on hundreds of files found by find, process
a list from a file or avoid the "argument list too long" error, xargs is
the essential tool. Simple and powerful, xargs turns standard input into
arguments for another command, so that commands chain together
efficiently.
What you will learn
- Turn stdin into arguments for a command
- Limit the number of arguments with -n
- Place the arguments with -I
- Handle file names with spaces (-0)
- Run commands in parallel with -P
The xargs command in the Linux ecosystem
xargs belongs to the pipeline commands of Linux. Each tool has its
speciality, and xargs is the bridge between a list and a command:
| Command | Speciality | When to use it |
|---|---|---|
xargs | Turn stdin into arguments | Run a command on a list |
find | Search for files | Find by name, size, date |
grep | Filter lines | Search for patterns |
parallel | Advanced parallel execution | Complex jobs, remote machines |
tee | Duplicate a stream | Write and display at the same time |
xargs -P | Simple parallelism | Local jobs in parallel |
When to use xargs vs find -exec?
The table compares the two ways of acting on the result of a find: the
pipe to xargs or the built-in -exec action:
| Situation | xargs | find -exec |
|---|---|---|
| Performance (fewer forks) | Yes, find | xargs cmd | No with \; (one fork per file), yes with {} + |
| Files with spaces | Requires -print0 | xargs -0 | Handled automatically |
| Parallel execution | Yes, -P N | No |
| Simple syntax | Yes | Yes |
| Without a pipe | No | Yes, -exec cmd {} \; |
Frequent combinations:
# find + xargs: delete files (safely)find . -name "*.tmp" -print0 | xargs -0 rm
# grep + xargs: act on the files containing a patterngrep -rl "TODO" src/ | xargs -I {} echo "À traiter: {}"
# cat + xargs: run on a list from a filecat urls.txt | xargs -P 4 -I {} curl -O {}
# ls + xargs: bulk renamels *.bak | xargs -I {} mv {} {}.oldUnderstand the xargs command in 2 minutes
The mental model below sums up how xargs works: a list on stdin, a
split, a target command.
🧠 Mental model: How xargs works
xargs = Read stdin → Split into arguments → Run the command with those arguments
Key points
- xargs reads stdin and turns each item into an argument
- By default, xargs uses spaces and newlines as separators
- With no command given, xargs uses echo by default
- -n limits the number of arguments per run
- -I {} lets you place the argument wherever you want
- -0 with find -print0 handles file names containing spaces
Golden rules
Essential vocabulary
- -n N
- At most N arguments per command
- -I {}
- Replace {} with each input line
- -0
- Null separator (with find -print0)
- -p
- Ask for confirmation before running
- -t
- Print the command before running it
- -r
- Do not run if stdin is empty
📚 Going further: 6 advanced options
- -P N
- Run N commands in parallel
- -L N
- At most N lines per command
- -a file
- Read from a file instead of stdin
- -d delim
- Use delim as the separator
- --max-procs
- Alias of -P
- --no-run-if-empty
- Alias of -r
xargs reads stdin, splits on spaces or newlines, then passes the items as
arguments to the given command.
Minimal syntax
Remember the reading order: what is left of the pipe produces text, what is
right of xargs receives that text as arguments. Every option goes
between xargs and the target command; an option written after the target
command would be passed to it as is, which causes hard-to-read errors.
commande_source | xargs [OPTIONS] commande_cibleThe data flow
This table serves as a diagnostic grid: when a chain does not give the expected result, first identify at which step it breaks. Nine times out of ten the problem sits at step 3, the splitting of the input, and not in the target command that you spontaneously blame.
| Step | Description | Example |
|---|---|---|
| 1. Source | Generates a list | find . -name "*.log" |
| 2. Pipe | Passes to xargs | | |
| 3. xargs | Splits and groups | xargs |
| 4. Target | Receives the arguments | rm → rm f1.log f2.log f3.log |
Default behaviour
Two default behaviours deserve to be known before any serious use. First,
without a target command, xargs calls echo: this is what makes it an
immediate preview tool. Next, xargs groups the maximum number of
arguments in a single call rather than calling the command once per item; this
grouping is what brings its performance gain and avoids the
Argument list too long error.
# Without a command: xargs uses echoecho "a b c" | xargs# Output: a b c
# With a command: passes every argumentecho "f1.txt f2.txt" | xargs rm# Equivalent to: rm f1.txt f2.txtEssential options
Seven options cover almost every use. Two are safety nets to use by reflex:
-0 against exotic file names and -r against running on empty input. Two
serve for debugging before committing a destructive action: -t and -p.
The other three drive the splitting and the parallelism.
| Option | Effect | Example |
|---|---|---|
-n N | At most N arguments per run | xargs -n 2 |
-I {} | Use as a placeholder | xargs -I {} mv {} {}.bak |
-0 | Null separator (with find -print0) | find -print0 | xargs -0 |
-p | Ask for confirmation | xargs -p rm |
-t | Print the command (trace) | xargs -t echo |
-r | Do not run if empty | xargs -r rm |
-P N | N processes in parallel | xargs -P 4 |
Limit the arguments with -n
By default, xargs passes every argument at once. -n limits the number
of arguments per run:
# Every argumentecho "1 2 3 4 5 6" | xargs echo# Output: 1 2 3 4 5 6
# 2 arguments per runecho "1 2 3 4 5 6" | xargs -n 2 echo# Output:# 1 2# 3 4# 5 6Place the argument with -I
By default, xargs appends the arguments at the end. -I {} lets you
place them anywhere in the command:
# Without -I: arguments at the endecho "file.txt" | xargs cp /backup/# → cp /backup/ file.txt (ERROR!)
# With -I: control over placementecho "file.txt" | xargs -I {} cp {} /backup/{}# → cp file.txt /backup/file.txt (correct)The point everyone misses: -I does not split on spaces but on
newlines. The GNU manual page states that -I implies -L 1, that is one
input line per run, and not -n 1 (one argument per run). The difference
shows immediately:
printf "a b\nc d\n" | xargs -I {} echo "[{}]"# [a b]# [c d] <- 2 runs, one per line
printf "a b\nc d\n" | xargs -n 1 echo "["# [ a# [ b# [ c# [ d <- 4 runs, one per wordIt is good news: -I correctly handles a file name containing a space as
long as it takes up a whole line. A corollary to know, -n and -I are
mutually exclusive: combining them makes the -n ineffective, and GNU
xargs only reports it when -n comes before -I, with the warning
options --max-args and --replace/-I/-i are mutually exclusive.
Handle spaces with -0
File names with spaces break xargs by default, which is why the -0
option exists, paired with find -print0:
# WRONG: DANGER: "mon fichier.txt" becomes 2 argumentsfind . -name "*.txt" | xargs rm# rm tries to delete "mon" and "fichier.txt" separately!
# RIGHT: SAFE: null separatorfind . -name "*.txt" -print0 | xargs -0 rm# Correctly handles "mon fichier.txt"Test before running
Golden rule: always test with echo or -p first. xargs very often feeds
rm, mv or chmod, commands with no way back. The three lines below form
a progression to follow in order: look, confirm, then run. Step 1 is the most
useful because it reveals the actual splitting of the input, that is exactly
what you do not see when the command runs directly.
# 1. See what would be runfind . -name "*.tmp" | xargs echo rm
# 2. Ask for confirmationfind . -name "*.tmp" | xargs -p rm
# 3. Run (if OK)find . -name "*.tmp" -print0 | xargs -0 rmParallel execution with -P
-P N lets xargs run up to N simultaneous processes. The gain is real on
tasks bound by input/output (download, compression) and close to zero on a
task that already saturates a core. Take the number of cores returned by
nproc as a starting point, then measure: beyond it, disk contention
cancels the benefit.
One limit to know before launching: xargs -P does not order the outputs.
The lines of the different processes arrive interleaved on the terminal,
because no buffer is reserved per process. If you need readable output,
redirect each run to its own file.
Second limit, the most misleading one: -P only spreads batches. Without
-n or -I, xargs puts every file in a single call, and a single
process runs. Measured on eight 40 MB files: xargs -0 -P 4 gzip takes
11.1 s, exactly as without -P, whereas xargs -0 -n 1 -P 4 gzip drops to
3.9 s.
# Download 4 URLs in parallel (-I implies one line per run)cat urls.txt | xargs -P 4 -I {} curl -O {}
# Compress files in parallel: -n 1 creates one batch per filefind . -name "*.log" -print0 | xargs -0 -n 1 -P 4 gzipTypical errors (and fixes)
These five symptoms cover most xargs tickets. Read the middle column before
the right one: the cause is almost always a misunderstanding of how xargs
split the input, not a defect of the target command. The first two rows are
the only potentially destructive ones.
| Error | Cause | Fix |
|---|---|---|
| "No such file" with spaces | Wrong splitting | -print0 | xargs -0 |
| Command run without an argument | Empty stdin | Add -r |
| "Argument list too long" | Too many files | xargs splits automatically (or -n) |
| not replaced | Missing -I | Add -I {} |
| Mixed output in parallel | Interleaved outputs | Redirect to separate files |
Common usage patterns
Now that you understand the logic of xargs, here are fourteen ready-to-use
patterns. Each one gives its general formula and a concrete example.
Basic transformation
Pass a list as arguments to a command. It is the basic behaviour: xargs
groups everything it receives and calls the command only once, with every
item as an argument.
echo "a b c" | xargs <commande> # formulaecho "file1.txt file2.txt" | xargs rm # examplecommande: the command that receives the arguments
Read from a file
Process a list of files stored in a text file, one item per line.
cat <liste> | xargs <commande> # formulacat fichiers.txt | xargs rm # exampleliste: text file containing the items to process
Limit the arguments
Run the command with a maximum number of arguments per run.
xargs -n N <commande> # formulaecho "1 2 3 4 5 6" | xargs -n 2 echo # example-n N: at most N arguments per run
Place the argument
Use the argument at a precise position of the command, with {} as the
placeholder.
xargs -I {} <commande avec {}> # formulals *.txt | xargs -I {} mv {} {}.bak # example-I {}: replaces{}with each input line (implies-L 1)
Null separator (spaces in names)
Safely handle file names containing spaces, with the null character.
find . -print0 | xargs -0 <commande> # formulafind . -name "*.txt" -print0 | xargs -0 rm # example-print0:findseparates the results with a null character-0:xargsreads that same null separator
Interactive confirmation
Ask for a confirmation before each run. The prompt is read on the
terminal, not on standard input already taken by the pipe, which
explains the failed to open /dev/tty error in a non-interactive script or a
scheduled task.
xargs -p <commande> # formulaecho "file1 file2" | xargs -p rm # example-p: asksy/nbefore running
Verbose mode
Print each command before running it. The trace goes to stderr, so it
does not pollute a redirection of standard output to a file: you can keep
-t in production without altering the collected result.
xargs -t <commande> # formulaecho "1 2 3" | xargs -t -n 1 echo # example-t: trace, prints the command onstderr
Avoid running on empty input
Run nothing if standard input is empty. Without -r, xargs calls the
command at least once even without any item, which gives stray errors
with rm and can be downright dangerous with a command that acts on the current
directory by default.
xargs -r <commande> # formulafind . -name "*.xyz" | xargs -r rm # example-r: no-run-if-empty
Parallel execution
Launch several instances of the command at the same time, with -P,
the parallelism option.
xargs -P N <commande> # formulacat urls.txt | xargs -P 4 -I {} curl -O {} # example-P N: up to N processes in parallel
find + xargs + rm
Delete the files found by find. It is the most common chain of the guide, and the most
destructive one: always test it by replacing rm with echo rm
before running it for real.
find <chemin> -name "<motif>" -print0 | xargs -0 rm # formulafind /tmp -name "*.tmp" -print0 | xargs -0 rm # example- The
-print0/-0pair makes the deletion safe
find + xargs + grep
Search for text in the files found. The advantage over grep -r is to filter
first on criteria that grep does not know: the date, the size or the
type of entry.
find . -name "<motif>" -print0 | xargs -0 grep "<texte>" # formulafind . -name "*.py" -print0 | xargs -0 grep "import" # examplexargspasses the whole list togrepin a single command
Bulk rename
Rename several files following a pattern. It is the typical case where
-I is essential, since mv expects the argument in two places: the
source and the destination.
ls <motif> | xargs -I {} mv {} <nouveau_motif> # formulals *.bak | xargs -I {} mv {} {}.old # example-I {}lets you reuse the argument twice (source and destination)
Create an archive
Archive the files found by find into a single archive.
find . -name "<motif>" -print0 | xargs -0 tar -czvf <archive> # formulafind . -name "*.log" -print0 | xargs -0 tar -czvf logs.tar.gz # examplexargspasses every file totarat once, as long as the list fits in a single batch. If it exceeds the size of the command line,xargsrerunstaron the next batch and overwrites the archive: only the last batch survives. On a large volume, prefertar --null --files-from=-, which reads the list on standard input without splitting.
Bulk permission change
Apply chmod to several files. The -type f of find is not a detail:
without it, chmod +x would also apply to directories, which already need
the execute bit to be traversed, and you would lose track of what you
actually changed.
find . -type f -name "<motif>" -print0 | xargs -0 chmod <mode> # formulafind . -type f -name "*.sh" -print0 | xargs -0 chmod +x # example-type frestricts to regular files
Pitfalls to avoid
Eight errors come back constantly with xargs. Several are destructive:
xargs often chains into rm, so the symptom can be a lost file.
File names with spaces
Spaces in a name split one file into several arguments.
find . -name "*.txt" | xargs rm # WRONG: breaks on "mon fichier.txt"Symptom: No such file error or partially deleted files. Cause: xargs
splits on spaces, mon fichier.txt becomes two arguments.
find . -name "*.txt" -print0 | xargs -0 rm # RIGHT: null separatorRunning on empty input
An empty list still triggers one run of the target command.
find . -name "*.xyz" | xargs rm # WRONG: runs "rm" even without any fileSymptom: error message or unexpected behaviour. Cause: xargs launches
the command even when the input is empty.
find . -name "*.xyz" | xargs -r rm # RIGHT: -r: do nothing if emptyRunning without testing
A destructive command fed by xargs deserves a preview first.
cat liste.txt | xargs rm # WRONG: immediate, irreversible deletionSymptom: files deleted by mistake. Cause: no check before running.
cat liste.txt | xargs echo rm # RIGHT: preview, then xargs -p rmExceeding the argument limit
The kernel caps the total size of the arguments of a process, and a shell glob can exceed it in one go.
rm /var/log/app/*.log # WRONG: the shell expands the pattern into a single command lineSymptom: Argument list too long error, returned by the shell before rm
even starts. Cause: the kernel limits the total size of the arguments of a
process (getconf ARG_MAX, 2,097,152 bytes on Debian 13). Measured on 200,000
files: ls big/*.log fails, whereas find big -type f | xargs ls runs without
an error, xargs splitting the list into batches itself.
find /var/log/app -name "*.log" -print0 | xargs -0 rm # RIGHT: xargs splits into batchesPlaceholder without -I
{} only means something to xargs once -I is declared.
ls *.txt | xargs mv {} backup/ # WRONG: {} taken literallySymptom: syntax error, {} not replaced. Cause: the {} placeholder is
only active if -I is declared.
ls *.txt | xargs -I {} mv {} backup/ # RIGHT: -I activates the placeholderMixed output in parallel
Parallel processes write to the same terminal without any order.
xargs -P 4 commande # WRONG: the outputs of the 4 processes interleaveSymptom: the lines of the different processes are mixed. Cause: no output synchronisation between parallel processes.
xargs -P 4 -I {} sh -c 'commande {} > {}.out' # RIGHT: one output per fileQuotes consumed by the shell
The shell removes the quotes before xargs reads anything.
echo "a b" "c d" | xargs -n 1 # WRONG: gives 4 arguments, not 2Symptom: the intended grouping disappears, you get four items instead of
two. Cause: contrary to what you often read, xargs honours the quotes
present in its input. The culprit is the shell, which removes the quotes
before echo even writes anything: xargs receives a b c d, without any
grouping mark. Proof by example, if the quotes actually reach xargs, they are
honoured:
printf '%s\n' '"a b" "c d"' | xargs -n 1 # prints 2 lines: a b, then c dIn practice you do not rely on this mechanism, because -0 and -d
disable quote processing. The explicit separator remains the reliable
method:
printf "a b\0c d" | xargs -0 -n 1 # RIGHT: null separator, no ambiguityPipe after -I
A pipe written after xargs belongs to the shell, not to each run.
ls | xargs -I {} echo {} | wc -l # WRONG: the pipe leaves the scope of xargsSymptom: the pipe applies to the output of xargs, not to each item.
Cause: the shell splits the line at the pipe before launching xargs:
wc -l is not part of the repeated command, it receives the overall output.
ls | xargs -I {} sh -c 'echo {} | wc -c' # RIGHT: sh -c wraps the pipeHands-on lab
Nothing replaces practice. Create the test environment below, then go
through the twelve steps. At the end, clean up with
rm -rf ~/xargs-lab.
Prepare the ground
This script creates a tree in ~/xargs-lab: documents (some of them
with spaces in the name), images, logs and lists to process.
# Create the labmkdir -p ~/xargs-lab/documents ~/xargs-lab/images ~/xargs-lab/logscd ~/xargs-lab
# Files with simple namesecho "Contenu fichier 1" > documents/rapport.txtecho "Contenu fichier 2" > documents/notes.txtecho "Contenu fichier 3" > documents/todo.txt
# Files with spaces in the name (problem case)echo "Fichier avec espace" > "documents/mon rapport.txt"echo "Autre fichier" > "documents/mes notes importantes.txt"
# Image filestouch images/photo1.jpg images/photo2.jpg images/photo3.pngtouch "images/vacances été.jpg"
# Log filesfor i in {1..5}; do echo "Log entry $i - $(date)" > logs/app$i.logdone
# List of files to processcat > fichiers.txt << 'EOF'documents/rapport.txtdocuments/notes.txtdocuments/todo.txtEOF
# File with URLscat > urls.txt << 'EOF'https://example.com/page1https://example.com/page2https://example.com/page3EOF
echo "Correct : Lab créé dans ~/xargs-lab"ls -laR ~/xargs-labThe twelve steps
Go through them in order: each step reuses what the previous one produced. The first four stay harmless, the next ones touch real lab files. After each command, compare the output you get with the explanation sentence that follows it; if they differ, it is almost always the splitting of the input that changed.
-
Default behaviour. Observe what
xargsdoes without a command.Terminal window echo "un deux trois" | xargsWithout a command,
xargspasses the arguments toecho, the output isun deux trois. -
Delete listed files (test). Preview the deletion of the files listed in
fichiers.txt.Terminal window cd ~/xargs-lab && cat fichiers.txt | xargs echo rmechoin front ofrmshows the command that would run, without deleting anything. -
Limit the arguments. Print the numbers 1 to 6, two per line.
Terminal window echo "1 2 3 4 5 6" | xargs -n 2 echo-n 2limits eachechocall to 2 arguments. -
Place the argument. Create a
.bakcopy of each.txtin thedocumentsdirectory.Terminal window ls ~/xargs-lab/documents/*.txt | xargs -I {} cp {} {}.bak-I {}replaces{}with each argument, which lets you use it twice. -
Interactive confirmation. Ask for a confirmation before each deletion.
Terminal window echo "test1.tmp test2.tmp" | xargs -p rm-pprints the command and waits fory/nbefore running it. -
Show the commands run. Trace each command before it runs.
Terminal window echo "1 2 3" | xargs -t -n 1 echo "Nombre:"-twrites the command onstderrjust before launching it. -
Handle spaces with
find -print0. List every.txtofdocuments, spaces included.Terminal window find ~/xargs-lab/documents -name "*.txt" -print0 | xargs -0 ls -l-print0and-0use the null character as the separator,mon rapport.txtstays a single argument. -
Do not run if empty. Avoid launching
rmif no.xyzfile exists.Terminal window find ~/xargs-lab -name "*.xyz" | xargs -r rm-rprevents the run when the input is empty. -
Parallel execution. Simulate the download of 3 URLs in parallel.
Terminal window cat ~/xargs-lab/urls.txt | xargs -P 3 -I {} echo "Downloading {}"-P 3launches up to 3 commands simultaneously. -
Combine find and xargs for grep. Search for
Contenuin every.txt.Terminal window find ~/xargs-lab/documents -name "*.txt" -print0 | xargs -0 grep "Contenu"xargspasses the whole list of files togrepin a single invocation. -
Complex pipeline with
sh -c. Count the lines of each log file.Terminal window ls ~/xargs-lab/logs/*.log | xargs -I {} sh -c 'echo -n "{} : "; wc -l < "{}"'sh -clets you use a pipe or several commands for each item. -
Compress files. Create a
tar.gzarchive of every.txt.Terminal window find ~/xargs-lab -name "*.txt" -print0 | xargs -0 tar -czvf ~/xargs-lab/archive.tar.gzxargspasses every file found totarat once.
Progressive exercises
Ten exercises to validate your mastery. They work on the ~/xargs-lab
lab. Search on your own first, then unfold the solution to check
yourself.
Foundation level
These four exercises cover the splitting and placement of arguments,
with no risk for your files. Each one is solved in one line. If you get
stuck, rerun the command with -t: the trace shows exactly what xargs built
before running.
Exercise 1, basic transformation. Print fichier1 fichier2 fichier3 on a single line with xargs. Hint: echo followed by xargs without an argument.
Show the solution
echo "fichier1 fichier2 fichier3" | xargsWithout a command, xargs uses echo and concatenates the arguments.
Exercise 2, limit the arguments. Print the numbers 1 to 6, two per line. Hint: -n limits the number of arguments.
Show the solution
echo "1 2 3 4 5 6" | xargs -n 2-n 2 runs echo with 2 arguments on each pass.
Exercise 3, place the argument. Create the files backup_1.txt, backup_2.txt, backup_3.txt. Hint: -I {} places the argument.
Show the solution
printf "1\n2\n3\n" | xargs -I {} touch backup_{}.txt-I {} replaces {} with each line of input, hence the printf with
newlines. The variant echo "1 2 3" | xargs -I {} touch backup_{}.txt
fails: since -I splits on newlines, it creates a single file named
backup_1 2 3.txt. Adding -n 1 fixes nothing: -n and -I are mutually
exclusive, and xargs ignores the -n.
Exercise 4, verbose mode. Print each echo command before it runs. Hint: -t for trace.
Show the solution
echo "a b c" | xargs -t -n 1 echo-t prints the command on stderr before running it.
Composition level
Now you move on to chaining several commands and to robustness. These four exercises reproduce the situations that break in production: file names with spaces, empty input, search over a computed list. The right answer always takes into account what the upstream command produces, not only what the downstream command must do.
Exercise 5, handle spaces. List the lab's .txt files, even those with spaces in the name. Hint: find -print0 + xargs -0.
Show the solution
find ~/xargs-lab -name "*.txt" -print0 | xargs -0 ls -l-print0 and -0 use the null character as the separator.
Exercise 6, avoid running on empty input. Do not run rm if no .xyz file exists. Hint: -r for no-run-if-empty.
Show the solution
find ~/xargs-lab -name "*.xyz" | xargs -r rm-r prevents the run when standard input is empty.
Exercise 7, search in the files found. Search for Contenu in every .txt of the lab. Hint: find + xargs + grep.
Show the solution
find ~/xargs-lab -name "*.txt" -print0 | xargs -0 grep "Contenu"xargs passes every .txt file found to grep.
Exercise 8, interactive confirmation. Ask for a confirmation before deleting each file. Hint: -p for prompt, -n 1 one file at a time.
Show the solution
echo "file1 file2" | xargs -p -n 1 rm-p asks for confirmation; -n 1 processes one file at a time.
Industrialisation level
The last two exercises step out of the "one command, one list" frame. They use
parallelism and wrapping with sh -c, the only way to use a pipe or a
redirection inside each run. Time exercise 9 with time: it is the most
telling demonstration of what -P brings.
Exercise 9, parallel execution. Simulate 4 jobs in parallel (a sleep then an echo). Hint: -P for parallelism.
Show the solution
printf "1\n2\n3\n4" | xargs -P 4 -I {} sh -c 'sleep 1; echo "Done {}"'-P 4 launches up to 4 processes simultaneously, the 4 finish almost at the same time.
Exercise 10, complex pipeline. For each .log file of the lab, count the lines containing Log. Hint: sh -c for pipes inside xargs.
Show the solution
find ~/xargs-lab -name "*.log" -print0 | xargs -0 -I {} sh -c 'echo -n "{} : "; grep -c "Log" "{}"'sh -c allows pipes and redirections for each processed item.
Troubleshooting
When xargs misbehaves, the problem almost always comes from the splitting of
the input (spaces, batches, placeholder). Here is how to diagnose it, then
how to read the error messages.
Diagnostic methods
These four commands run in this order. The first three make visible what xargs
actually builds; the fourth gives the system limit beyond which the
kernel refuses the command line. On a recent machine, getconf ARG_MAX often
returns more than two million bytes, but xargs --show-limits gives the value
that really matters: the size of the buffer actually used, 131,072 bytes by
default with GNU findutils 4.10, well below the allowed maximum.
# See what would be run, without running anythingfind . -name "*.tmp" | xargs echo rm
# Print each command before running itecho "a b c" | xargs -t -n 1 echo
# Ask for confirmation before each runcat liste.txt | xargs -p rm
# Know the maximum argument size of the systemgetconf ARG_MAX
# See the limit actually applied by xargs on this machinexargs --show-limits < /dev/nullFrequent errors
Four messages come back over and over. What they have in common: the errors
often carry the name of the target command as a prefix, which makes you
believe in a problem with rm or grep. Always check the input before
blaming the command, by replaying it with xargs echo in front.
| Error message | Probable cause | Fix |
|---|---|---|
Argument list too long | Shell pattern (rm *.log) expanded into too many arguments | Go through find … -print0 | xargs -0, which splits into batches |
xargs: argument line too long | A single input item exceeds the buffer size | Check the separator: an input without newlines is read as one block |
No such file or directory | Spaces or special characters in the names | Use find -print0 | xargs -0 |
xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option | Apostrophe in a file name | Use -0 to ignore special characters |
xargs cheat sheet
The two tables below gather the options and chains you will type every day, to keep at hand while writing a command.
Options
This list extends the table of the seven essential options with three entries
useful every day: -L splits by input lines and not by words, -a avoids
the useless cat by reading a file directly, -d changes the separator when
the input is a CSV or a variable. Watch -d and -0: both disable quote
and backslash processing.
| Option | Role | Example |
|---|---|---|
-n N | At most N arguments per run | xargs -n 2 |
-I {} | {} as a placeholder | xargs -I {} mv {} {}.bak |
-0 | Null separator (with find -print0) | find . -print0 | xargs -0 |
-p | Confirmation before running | xargs -p rm |
-t | Prints the command (trace) | xargs -t echo |
-r | Run nothing if the input is empty | xargs -r rm |
-P N | N processes in parallel | xargs -P 4 |
-L N | At most N lines per run | xargs -L 1 |
-a fichier | Read from a file instead of stdin | xargs -a liste.txt rm |
-d delim | Set the separator | xargs -d "," |
Common chains
Four patterns to memorise as they are. The second, find -print0 | xargs -0, is
the one to write by default as soon as files are involved: it costs six more
characters and removes a whole class of incidents. The third, sh -c, is the
only way to use a pipe or a redirection inside each run.
| Chain | Role | Example |
|---|---|---|
cmd | xargs | Turn stdin into arguments | echo "a b" | xargs echo |
find -print0 | xargs -0 | Safe pattern for files | find . -name "*.txt" -print0 | xargs -0 rm |
xargs -I {} sh -c '…' | Complex command with pipes | xargs -I {} sh -c 'cat {} | wc -l' |
cmd | xargs echo | Test without running | cat liste | xargs echo rm |
Mastery checklist
You master xargs when you can tick each of these points, from splitting to
parallelism and sh -c wrapping:
- I can turn standard input into arguments
- I can limit the number of arguments with
-n - I can place the argument with
-I {} - I can debug with
-tand-p - I can handle names with spaces thanks to
-0 - I can avoid running on empty input with
-r - I can combine
find,xargsandgrep - I can run in parallel with
-P - I can wrap a pipe with
sh -c
Conclusion
The xargs command is the missing link between the commands that produce
lists (find, grep -l, cat) and those that expect arguments (rm, mv,
cp). Its key options are -0 (safety with spaces), -I {}
(placement), -n (limitation) and -P (parallelism). For more advanced
needs (remote machines, complex jobs), explore
parallel.
Articles you may find useful
Key points
xargsturns standard input into arguments, grouped as much as possible in each call.- Without a target command,
xargscallsecho: it is the immediate preview tool. find -print0 | xargs -0is the default pattern as soon as files are involved.-I {}places the argument wherever you want, one input line per run, and makes-nineffective.-ravoids running the command on empty input,-pand-tserve for debugging.-P Nparallelises, without ordering the outputs, and only when there are several batches: add-n 1or-I.xargssplits into batches underARG_MAX: it is the cure forArgument list too long, not its cause.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: GNU Parallel: run jobs in parallel
A new full quiz with random questions
Work again only on the questions you missed
FAQ: frequently asked questions
The questions below cover the points that come up most often about xargs: the
basic syntax, the handling of file names containing spaces, the behaviour on
empty input, the argument limit and parallelism.
Definition
xargs (eXtended ARGumentS) turns standard input (stdin) into command-line arguments to run commands on lists of items.| Feature | Description |
|---|---|
| Function | Convert stdin → CLI arguments |
| Typical use | Pipeline with find, grep, ls |
| Problem solved | ARG_MAX limit (maximum argument size) |
| Processing | Automatic batch processing |
| Safety | Handles special file names (-0) |
Pipeline concept
# Without xargs (impossible)
rm $(find . -name "*.tmp") # ❌ Fails if too many files
# With xargs (robust)
find . -name "*.tmp" | xargs rm # ✅ Automatic batches
Basic examples
# 1. Count files
find . -name "*.txt" | xargs wc -l
# 2. Copy files
ls *.log | xargs -I {} cp {} /backup/
# 3. Search for a pattern
find . -type f | xargs grep "ERROR"
# 4. Delete old files
find /tmp -mtime +7 | xargs rm
DevOps cases
- Cleanup: delete old logs, caches, builds
- Backup: archive modified files
- Analysis: grep a pattern across several Git repos
- CI/CD: process artifacts, Docker images
- Monitoring: parse distributed logs
The ARG_MAX problem
# System argument limit
getconf ARG_MAX # E.g. 2097152 bytes (2 MB)
# ❌ FAILS if too many files
rm *.log # Error: Argument list too long
# ✅ WORKS with xargs
find . -name "*.log" | xargs rm
xargs advantages
| Advantage | Description | Example |
|---|---|---|
| Batch processing | Splits into batches automatically | xargs -n 100 |
| ARG_MAX limit | Works around the system limit | Unlimited files |
| Parallelisation | Simultaneous execution (-P) | xargs -P 4 |
| Name safety | Handles spaces/newlines (-0) | xargs -0 |
| Performance | Fewer fork() than loops | 8.6 s versus 0.08 s on 10,000 files (measured) |
Loop vs xargs
# ❌ Shell loop (slow, 1 fork per file)
for file in *.txt; do
gzip "$file"
done
# ✅ xargs (fast, batch processing)
ls *.txt | xargs gzip
# ✅ Parallel xargs (very fast)
ls *.txt | xargs -n 1 -P 4 gzip # 4 simultaneous processes, one file each
Practical cases
# CI/CD: clean old Docker images
docker images -q -f "dangling=true" | xargs docker rmi
# Logs: compress old files
find /var/log -name "*.log" -mtime +30 | xargs gzip
# Kubernetes: delete failed pods
kubectl get pods -o name | grep Failed | xargs kubectl delete
# Git: search a pattern in every repo
find ~/projects -name .git -type d | xargs -I {} git -C {} log --grep="bug"
Performance
# Measured on Debian 13: 10,000 small files
time for f in *.txt; do wc -l "$f"; done # 8.6 s (one process per file)
time ls *.txt | xargs wc -l # 0.08 s (a single batch)
time ls *.txt | xargs -P 4 wc -l # 0.07 s (-P without -n: a single batch, no gain)
Rule: xargs is a must for bulk operations (>1000 files).Syntax structure
# Basic pattern
commande_source | xargs [OPTIONS] commande_destination
└─stdin └─args └─run with the args
# Examples
echo "file1 file2" | xargs ls -l
find . -name "*.log" | xargs grep ERROR
cat urls.txt | xargs curl -O
Main options
| Option | Use | Example |
|---|---|---|
-n NUM |
Max arguments per run | xargs -n 5 |
-I {} |
Placeholder replacement | xargs -I {} mv {} /dest/ |
-0 |
Null-terminated input | find -print0 | xargs -0 |
-P NUM |
Parallel processes | xargs -P 4 |
-t |
Print command before running | xargs -t rm |
-p |
Prompt before running | xargs -p rm |
-r |
No run if empty | xargs -r command |
Common stdin sources
# find (the most frequent)
find /var/log -name "*.log" | xargs grep ERROR
# cat a file
cat file_list.txt | xargs cp -t /backup/
# echo
echo "file1 file2 file3" | xargs ls -l
# ls
ls *.txt | xargs wc -l
# grep
grep -l "TODO" *.md | xargs vim
Progressive examples
# 1. Basic (echo by default)
echo "a b c" | xargs
# Output: a b c
# 2. Simple command
echo "file1.txt file2.txt" | xargs cat
# 3. With options
echo "1 2 3 4" | xargs -n 2 echo
# Output: 1 2
# 3 4
# 4. Placeholder
echo "*.log" | xargs -I {} mv {} /backup/{}
# 5. Parallel
find . -name "*.jpg" | xargs -P 4 -I {} convert {} {}.png
DevOps patterns
# Docker cleanup
docker ps -a -q -f status=exited | xargs docker rm
# Kubernetes logs
kubectl get pods -o name | xargs -I {} kubectl logs {}
# Git multi-repo
find ~/projects -name .git | xargs -I {} git -C {}/../ pull
Pattern: source | xargs [options] destination.The -n option (max-args)
# Syntax
xargs -n NUMBER command
# Basic example
echo "1 2 3 4 5 6" | xargs -n 2 echo
# Runs:
# echo 1 2
# echo 3 4
# echo 5 6
Behaviour
| -n VALUE | Effect | Runs |
|---|---|---|
-n 1 |
1 arg per command | As many runs as args |
-n 5 |
5 args per command | Total args / 5 |
-n 100 |
100 args max | Batches of 100 |
| (default) | Every possible arg | 1 run (or according to ARG_MAX) |
Practical examples
# Process files one by one
ls *.txt | xargs -n 1 gzip
# gzip file1.txt
# gzip file2.txt
# gzip file3.txt
# Copy in pairs
echo "src1 dest1 src2 dest2" | xargs -n 2 cp
# cp src1 dest1
# cp src2 dest2
# Batches of 50 files per call
find . -name "*.log" -print0 | xargs -0 -n 50 gzip
DevOps cases
# Docker: remove images 10 by 10
docker images -q | xargs -n 10 docker rmi
# Kubernetes: restart pods one by one (rolling)
kubectl get pods -o name | xargs -n 1 kubectl delete
# Monitoring: check 5 URLs simultaneously
cat urls.txt | xargs -n 1 -P 5 curl -I
# Backup: archive in groups of 100 files
find /data -type f | xargs -n 100 tar -rf backup.tar
Combining -n and -P
# 4 processes, 1 file each (-I implies one line per run)
ls *.mp4 | xargs -P 4 -I {} ffmpeg -i {} {}.webm
# Controlled parallelism
find . -name "*.jpg" | xargs -n 5 -P 2 mogrify -resize 50%
# 2 simultaneous processes, 5 images each
Debug with -n 1
# See each run
find . -name "*.tmp" | xargs -n 1 -t rm
# rm ./file1.tmp
# rm ./file2.tmp
# ...
Tip: -n 1 = the safest (1 arg = 1 run), useful for debugging.The -p option (prompt)
# Syntax
xargs -p command
# Example
find . -name "*.tmp" | xargs -p rm
# rm ./cache.tmp ?...y
# rm ./old.tmp ?...n (skip)
# rm ./test.tmp ?...y
Interactive workflow
# 1. Prints the full command
echo "file1 file2" | xargs -p rm
# Prompt: rm file1 file2 ?...
# 2. Waits for the user's answer
# y/Y = yes → runs
# n/N = no → skip
# Ctrl+C = abort everything
# 3. Repeats for each batch
Safety use cases
# Safe deletion
find /var/log -name "*.log" -mtime +90 | xargs -p rm
# Database change
cat user_ids.txt | xargs -p -I {} psql -c "DELETE FROM users WHERE id={}"
# Docker cleanup
docker ps -a -q | xargs -p docker rm
# Kubernetes delete
kubectl get pods -o name | grep test | xargs -p kubectl delete
Combining -p and -n
# Confirm file by file
ls *.bak | xargs -n 1 -p rm
# rm file1.bak ?...y
# rm file2.bak ?...n
# rm file3.bak ?...y
Alternative: -t (verbose)
# -t: prints without asking
find . -name "*.tmp" | xargs -t rm
# rm ./cache.tmp ./old.tmp ./test.tmp
# (runs directly)
# Combining -t + dry run
find . -name "*.log" | xargs -t echo rm
# echo rm file1.log file2.log
# (simulation, no real run)
Safety pattern
# 1. Test with echo
find . -name "*.old" | xargs echo rm
# 2. Check with -t
find . -name "*.old" | xargs -t rm --dry-run 2>/dev/null || true
# 3. Run with -p
find . -name "*.old" | xargs -p rm
# 4. Final run
find . -name "*.old" | xargs rm
Rule: always -p or -t for destructive commands (rm, docker rmi, kubectl delete).The -I option (replace-str)
# Syntax
xargs -I PLACEHOLDER command PLACEHOLDER [PLACEHOLDER ...]
# Convention: {} as the placeholder
xargs -I {} command arg1 {} arg2 {}
Basic examples
# Move files
ls *.txt | xargs -I {} mv {} /backup/{}
# Copy with renaming
ls *.log | xargs -I {} cp {} {}.backup
# Create a tree
cat dirs.txt | xargs -I {} mkdir -p /data/{}
# Multiple occurrences
echo "app" | xargs -I {} docker run -v {}:/app --name {} myimage
Common patterns
| Pattern | Command | Result |
|---|---|---|
| Backup | xargs -I {} cp {} {}.bak |
file.txt → file.txt.bak |
| Rename | xargs -I {} mv {} new_{} |
file.txt → new_file.txt |
| Directory | xargs -I {} mv {} /dest/{} |
Moves to /dest/ |
| Extension | xargs -I {} convert {} {}.png |
.jpg → .jpg.png |
| Multiple | xargs -I {} cp {} /a/{} /b/{} |
Copies to 2 destinations |
Name transformation
# Prefix
ls *.md | xargs -I {} mv {} docs_{}
# Suffix
ls *.jpg | xargs -I {} convert {} {}_thumb.jpg
# Substitution (with bash)
ls *.txt | xargs -I {} bash -c 'mv {} $(echo {} | sed s/.txt/.md/)'
# Extension change
find . -name "*.jpeg" | xargs -I {} mv {} {}.jpg
DevOps cases
# Docker: tag images
docker images -q | xargs -I {} docker tag {} registry.io/{}:latest
# Kubernetes: copy secrets (the redirection must live inside sh -c)
kubectl get secrets -o name | xargs -I {} sh -c 'kubectl get {} -o yaml > "$(basename {}).yaml"'
# Git: clone several repos
cat repos.txt | xargs -I {} git clone https://github.com/org/{}
# Backup with a timestamp
find . -name "*.conf" | xargs -I {} cp {} {}.$(date +%Y%m%d)
# AWS S3: download objects
aws s3 ls s3://bucket/ | awk '{print $4}' | xargs -I {} aws s3 cp s3://bucket/{} ./{}
Custom placeholder
# Use another symbol than {}
ls *.log | xargs -I FILE mv FILE /backup/FILE
ls *.txt | xargs -I @ echo "Processing @"
Versus positional args
# Without -I (args at the end)
ls *.txt | xargs grep ERROR
# grep ERROR file1.txt file2.txt
# With -I (position control)
ls *.txt | xargs -I {} grep ERROR {}
# grep ERROR file1.txt
# grep ERROR file2.txt
Tip: -I {} lets you reuse the same argument several times in the command.The spaces/newlines problem
# ❌ FAILS with spaces
find . -name "*.txt" | xargs rm
# File: "My Document.txt"
# Read as: rm My Document.txt (2 files!)
# ✅ WORKS with -0
find . -name "*.txt" -print0 | xargs -0 rm
# File processed correctly: "My Document.txt"
Separators
| Method | Separator | Problems |
|---|---|---|
| Default | Space/tab/newline | ❌ Breaks names with spaces |
| -0 | Null byte (\0) |
✅ Safe for every character |
find -print0 + xargs -0
# Standard pattern (ALWAYS use it)
find [path] [tests] -print0 | xargs -0 [command]
# Examples
find . -name "* *.txt" -print0 | xargs -0 rm
find /backup -mtime +30 -print0 | xargs -0 gzip
find . -type f -print0 | xargs -0 grep "ERROR"
Problem cases
# Files with:
# - Spaces: "My File.txt"
# - Newlines: "File\nWith\nBreaks.txt"
# - Tabs: "File\tName.txt"
# - Quotes: "File'With"Quotes.txt"
# ❌ Without -0
find . -type f | xargs wc -l # FAILS
# ✅ With -0
find . -type f -print0 | xargs -0 wc -l # OK
Other null-terminated sources
# GNU tools support -0
grep -lZ "pattern" *.txt | xargs -0 vim
locate -0 "*.conf" | xargs -0 cat
sort -z file.txt | xargs -0 process
# tr to convert
cat list.txt | tr '\n' '\0' | xargs -0 rm
DevOps examples
# Git: modified files with spaces
git ls-files -z -m | xargs -0 git add
# Docker: cleanup with labels
docker ps -aq --filter "label=env=test" | tr '\n' '\0' | xargs -0 docker rm
# Kubernetes: pod logs with spaces in the namespace
kubectl get pods -o name | tr '\n' '\0' | xargs -0 -I {} kubectl logs {}
# Backup of recent files (tar reads the list itself, without xargs)
find /data -mtime -1 -print0 | tar --null -czf backup.tar.gz --files-from=-
Safety test
# Create a test file with spaces
touch "My Test File.txt"
# ❌ Without -0 (detects 3 files!)
find . -name "*.txt" | xargs ls
# ls: My: No such file
# ls: Test: No such file
# ls: File.txt: No such file
# ✅ With -0 (OK)
find . -name "*.txt" -print0 | xargs -0 ls
# ./My Test File.txt
CRITICAL RULE: ALWAYS -print0 | xargs -0 with find to avoid bugs.The -t option (verbose/trace)
# Syntax
xargs -t command
# Example
echo "file1 file2" | xargs -t rm
# Output on stderr: rm file1 file2
# (then runs)
Behaviour
# Prints the full command on stderr
find . -name "*.tmp" | xargs -t rm
# rm ./cache.tmp ./old.tmp
# rm ./test.tmp
# Each batch printed
echo "1 2 3 4" | xargs -n 2 -t echo
# echo 1 2
# 1 2
# echo 3 4
# 3 4
Debug use cases
# Check the arguments passed
find /var/log -name "*.log" | xargs -t wc -l
# Prints: wc -l /var/log/app.log /var/log/system.log ...
# Trace multiple runs
ls *.txt | xargs -n 1 -t gzip
# gzip file1.txt
# gzip file2.txt
# gzip file3.txt
Production logging
# Redirect traces to a log
find . -name "*.old" | xargs -t rm 2>> cleanup.log
# Timestamped logs
find /data -mtime +30 | xargs -t -I {} \
bash -c 'echo "$(date): Deleting {}" >> audit.log && rm {}'
# Progressive monitoring
find . -name "*.jpg" | xargs -t -P 4 -I {} convert {} {}.png 2>&1 | tee progress.log
Dry-run simulation
# Test without a real run
find . -name "*.bak" | xargs -t echo rm
# echo rm file1.bak file2.bak
# file1.bak file2.bak
# (prints but does NOT run rm)
# Dry-run alternative
find . -type f | xargs -t -I {} echo "Would delete: {}"
DevOps patterns
# Docker: see removed images
docker images -q -f dangling=true | xargs -t docker rmi
# docker rmi sha256:abc123 sha256:def456
# Kubernetes: trace deletions
kubectl get pods -o name | grep test | xargs -t kubectl delete
# kubectl delete pod/test-1 pod/test-2
# CI/CD: log deployment steps
cat artifacts.txt | xargs -t -I {} scp {} deploy@server:/app/
# scp app.jar deploy@server:/app/
# scp config.yml deploy@server:/app/
# Git: trace multi-repo pulls
find ~/projects -name .git -type d | xargs -t -I {} git -C {}/../ pull
Combining -t and -p
# Print AND confirm
find . -name "*.tmp" | xargs -t -p rm
# rm ./cache.tmp ?...y
# (prints then asks for confirmation)
Performance monitoring
# With time for each command
ls *.log | xargs -n 1 -t -I {} bash -c 'time gzip {}'
# Parallel with traces
find . -name "*.mp4" | xargs -t -P 4 -I {} ffmpeg -i {} {}.webm
Tip: -t is essential to debug complex find | xargs pipelines.Default behaviour: echo
# Without a command → implicit echo
echo "a b c" | xargs
# Output: a b c
# Equivalent to
echo "a b c" | xargs echo
# Output: a b c
Usefulness: test and debug
# 1. Test a pipeline before running it
find . -name "*.tmp" | xargs
# Prints: ./cache.tmp ./old.tmp ./test.tmp
# (instead of deleting them)
# 2. Check the arguments
cat files.txt | xargs
# Shows what would be passed as args
# 3. Format output
ls | xargs
# file1
# file2 → file1 file2 (on 1 line)
# file3
Practical cases
# Concatenate lines
cat list.txt | xargs
# line1
# line2 → line1 line2 line3
# line3
# Count words after a filter
grep "ERROR" logs.txt | cut -d: -f2 | xargs | wc -w
# Format for copy-paste
kubectl get pods -o name | xargs
# pod/app-1 pod/app-2 pod/app-3 (easy to copy)
Test before destroying
# 1. Test with xargs (echo)
find . -name "*.bak" | xargs
# ./file1.bak ./file2.bak
# 2. Check OK → run
find . -name "*.bak" | xargs rm
Dry-run pattern
# Simulate a command
DRY_RUN=echo # or empty for a real run
find . -name "*.log" | xargs $DRY_RUN rm
# With DRY_RUN=echo:
# rm file1.log file2.log (printed, not run)
# With DRY_RUN="" :
# (real run)
DevOps examples
# Docker: list image IDs
docker images -q | xargs
# abc123 def456 ghi789
# Git: modified files on 1 line
git ls-files -m | xargs
# file1.js file2.py README.md
# AWS: format instance IDs
aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' \
--output text | xargs
Tip: xargs alone = the best dry run before destructive commands.The -r option (--no-run-if-empty)
# Without -r (runs even if empty)
echo "" | xargs rm
# rm (runs without args → may cause an error)
# With -r (skip if empty)
echo "" | xargs -r rm
# (nothing run)
Problems without -r
# Commands that fail without args
find /inexistant -name "*.log" | xargs rm
# rm: missing operand
# With -r (robust)
find /inexistant -name "*.log" | xargs -r rm
# (skips silently)
Robust scripts
# ❌ Without -r (may fail)
#!/bin/bash
find /backup -mtime +30 | xargs rm
# If no file → error
# ✅ With -r (safe)
#!/bin/bash
find /backup -mtime +30 | xargs -r rm
# If no file → goes on
Exit codes
# Without -r: xargs returns 123 as soon as a run fails
echo "" | xargs rm; echo $?
# 123 (rm failed: missing operand)
# With -r: exit code 0 if skipped
echo "" | xargs -r rm; echo $?
# 0 (no error)
CI/CD patterns
# Docker cleanup (safe)
docker ps -q -f status=exited | xargs -r docker rm
# If no container → OK
# Kubernetes delete pods
kubectl get pods -o name | grep Failed | xargs -r kubectl delete
# If no Failed pod → goes on
# Git cleanup branches
git branch --merged | grep -v "main" | xargs -r git branch -d
# If no branch → OK
Conditional pipelines
# Clean only if files exist
find /tmp -name "*.tmp" -mtime +7 | xargs -r rm
# Backup only if files were modified (a single batch expected)
find /data -mtime -1 | xargs -r tar -czf backup.tar.gz
# Alert only on errors
grep ERROR /var/log/app.log | xargs -r -I {} echo "Alert: {}" | mail -s "Errors" admin@
Existence test
# Check before processing
RESULT=$(find . -name "*.pdf")
if [ -n "$RESULT" ]; then
echo "$RESULT" | xargs process
fi
# Or simply
find . -name "*.pdf" | xargs -r process
Robust logging
# Log only if there are files
find /backup -type f | xargs -r -I {} echo "Found: {}" >> backup.log
# With a count
COUNT=$(find . -name "*.log" | wc -l)
if [ $COUNT -gt 0 ]; then
echo "Processing $COUNT files"
find . -name "*.log" | xargs -r gzip
fi
Rule: always -r in scripts for robustness (no error on empty input).The cat | xargs pattern
# The liste.txt file contains:
# /tmp/file1.log
# /tmp/file2.log
# /var/cache/old.tmp
cat liste.txt | xargs rm
Robust options
# With space handling
cat liste.txt | xargs -d '\n' rm
# With confirmation
cat liste.txt | xargs -p rm
# With verbose
cat liste.txt | xargs -t rm
# Safe combination
cat liste.txt | xargs -r -t -p rm
File formats
# 1. One file per line (standard)
/path/to/file1
/path/to/file2
cat list.txt | xargs rm
# 2. Several per line (space-separated)
file1 file2 file3
file4 file5
cat list.txt | xargs rm
# 3. Null-terminated
file1\0file2\0file3
cat list.txt | xargs -0 rm
Dynamic generation
# Create the list then process it
find /var/log -name "*.log" -mtime +30 > old_logs.txt
# Review manually
cat old_logs.txt | xargs rm
# Or combine
find . -name "*.tmp" | tee to_delete.txt | xargs rm
DevOps cases
# Docker: remove images from a list
cat docker_images_to_remove.txt | xargs docker rmi
# Kubernetes: delete resources
cat k8s_resources.txt | xargs kubectl delete
# resources.txt contains:
# pod/app-1
# pod/app-2
# service/old-svc
# Git: delete branches
cat branches_to_delete.txt | xargs -I {} git branch -D {}
# AWS: terminate instances
cat instance_ids.txt | xargs aws ec2 terminate-instances --instance-ids
Batch operations
# Download URLs
cat urls.txt | xargs -n 1 -P 4 curl -O
# Copy files
cat files_to_backup.txt | xargs -I {} cp {} /backup/
# Change permissions
cat sensitive_files.txt | xargs chmod 600
# Pattern search
cat source_files.txt | xargs grep -l "TODO"
Advanced filtering
# Ignore comments and empty lines
grep -v '^#' list.txt | grep -v '^$' | xargs rm
# Variable expansion
cat paths.txt | envsubst | xargs rm
# paths.txt: $HOME/tmp/cache
# With a transformation
cat list.txt | sed 's/^/\/var\/log\//' | xargs rm
CI/CD manifests
# Deploy several manifests
cat k8s_manifests.txt | xargs -I {} kubectl apply -f {}
# manifests.txt:
# deployment.yaml
# service.yaml
# configmap.yaml
# Terraform modules
cat terraform_modules.txt | xargs -I {} terraform init {}
# Ansible playbooks
cat playbooks.txt | xargs -I {} ansible-playbook {}
Safety review
# 1. Generate the list
find . -name "*.old" > to_delete.txt
# 2. Manual review
vim to_delete.txt
# 3. Run with confirmation
cat to_delete.txt | xargs -p rm
# 4. Or dry run first
cat to_delete.txt | xargs echo rm
Pattern: cat list.txt | xargs command = batch processing from a file.By default: sequential
# Without -P: 1 command at a time
ls *.mp4 | xargs -I {} ffmpeg -i {} {}.webm
# Processes video1, then video2, then video3... (slow)
The -P option (parallel)
# Syntax
xargs -P NUM_PROCESSES command
# Examples
ls *.mp4 | xargs -P 4 -I {} ffmpeg -i {} {}.webm
# 4 simultaneous conversions
find . -name "*.jpg" | xargs -P 8 -I {} convert {} {}.png
# 8 conversions in parallel
Find the optimal number
# Number of CPU cores
NUM_CORES=$(nproc)
echo $NUM_CORES # E.g. 8
# CPU-bound: use every core
find . -name "*.jpg" | xargs -P $(nproc) -I {} convert {} {}.webm
# I/O-bound: 2-4x cores
find . -name "*.log" | xargs -n 1 -P $(($(nproc) * 2)) gzip
# Network-bound: 10-50 depending on bandwidth
cat urls.txt | xargs -P 20 -n 1 curl -O
Performance comparison
# Measured on Debian 13: 8 files of 40 MB, 16 cores
# Sequential
time ls *.log | xargs gzip
# real: 11.5 s
# -P 4 alone: a single batch, so a single gzip
time ls *.log | xargs -P 4 gzip
# real: 11.7 s (no gain)
# -P 4 with -n 1: one batch per file
time ls *.log | xargs -n 1 -P 4 gzip
# real: 3.9 s (about 3 times faster)
DevOps cases
# Docker: build several images
cat Dockerfiles.txt | xargs -P 4 -I {} docker build -f {} .
# Kubernetes: check pod health
kubectl get pods -o name | xargs -P 10 -I {} kubectl exec {} -- curl -f localhost:8080/health
# Git: pull several repos
find ~/projects -name .git -type d | xargs -P 8 -I {} git -C {}/../ pull
# AWS: download S3 objects
aws s3 ls s3://bucket/ | awk '{print $4}' | xargs -P 16 -I {} aws s3 cp s3://bucket/{} ./
# Backup: compress logs
find /var/log -name "*.log" -mtime +7 | xargs -n 1 -P $(nproc) gzip
Combining -P and -n
# 4 processes, 5 files each
ls *.txt | xargs -P 4 -n 5 gzip
# Efficient pattern
find . -name "*.jpg" | xargs -P 8 -I {} mogrify -resize 50% {}
# 8 processes, 1 image each (-I implies one line per run)
Monitoring progress
# With verbose
find . -name "*.mp4" | xargs -P 4 -t -I {} ffmpeg -i {} {}.webm
# With one message per finished file
ls *.jpg | xargs -P 4 -I {} sh -c 'convert {} {}.png && echo "fini : {}"'
# With pv (progress bar)
find . -name "*.log" | pv -l -s $(find . -name "*.log" | wc -l) | xargs -P 8 gzip
Resource limits
# Limit CPU (with nice)
find . -name "*.jpg" | xargs -P 4 -I {} nice -n 10 convert {} {}.png
# Limit memory
find . -name "*.log" | xargs -P 2 -I {} bash -c 'ulimit -v 1000000 && gzip {}'
# Limit I/O (ionice)
find /backup -type f | xargs -P 4 -I {} ionice -c 3 cp {} /archive/
-P 0: unlimited
# As many processes as items (DANGEROUS!)
ls *.txt | xargs -P 0 -I {} process {}
# Can saturate the system if there are too many files
Recommendation: -P $(nproc) for CPU-bound, -P $((nproc*2)) for I/O-bound.The ARG_MAX system limit
# Check the limit
getconf ARG_MAX
# E.g. 2097152 (2 MB on modern Linux)
# Older values
# Old Linux: 131072 (128 KB)
# macOS: 262144 (256 KB)
# Solaris: 1048576 (1 MB)
The problem without xargs
# ❌ FAILS if too many files
rm /var/log/*.log
# bash: /bin/rm: Argument list too long
# Rough calculation
# 25,000 files × 100 characters/name = 2.5 MB > 2,097,152 bytes
xargs works around the limit
# ✅ xargs splits automatically
find /var/log -name "*.log" | xargs rm
# Runs rm several times with batches
# rm file1 file2 ... file999
# rm file1000 file1001 ... file1999
# ...
xargs batch size
# xargs computes the optimal size (real output, Debian 13)
xargs --show-limits < /dev/null
# POSIX upper limit on argument length (this system): 2094956
# Maximum length of command we could actually use: 2094808
# Size of command buffer we are actually using: 131072
# Force the maximum argument size
xargs -s 4096 command # Max 4096 bytes per run
Limit test
# Generate many files (long names to exceed ARG_MAX)
seq -f 'fichier_de_log_numero_%07g.log' 1 200000 | xargs touch
# ❌ Fails without xargs (measured: 200,000 names of 33 characters)
ls fichier_* | wc -l # Error: Argument list too long
rm fichier_* # Error: Argument list too long
# ✅ OK with xargs
find . -name "fichier_*" -print0 | xargs -0 rm # Works
# Or better
find . -name "fichier_*" -delete
Problem cases
# Large directories
ls /var/log/archive/*.gz # May fail
find /var/log/archive -name "*.gz" | xargs ls -lh # OK
# Multiple wildcards
grep pattern *.txt *.md *.log # Risk of hitting the limit
find . -name "*.txt" -o -name "*.md" | xargs grep pattern # Safe
Alternative solutions
# 1. find -exec (always safe)
find . -name "*.tmp" -exec rm {} +
# 2. find -delete (for rm)
find . -name "*.tmp" -delete
# 3. while read loop (slow but safe)
find . -name "*.log" | while read file; do rm "$file"; done
# 4. xargs (fast and safe)
find . -name "*.log" | xargs rm
DevOps scale
# Docker: millions of images
docker images -q | xargs -n 100 docker rmi
# Batches of 100 to avoid the limit
# Kubernetes: bulk pods
kubectl get pods -A -o name | xargs -n 50 kubectl delete
# Logs: thousands of files
find /var/log -type f -mtime +365 | xargs -n 1000 rm
# Git: large monorepo
git ls-files | xargs -n 500 git add
Safety calculation
# Check before running
ARG_MAX=$(getconf ARG_MAX)
FILE_COUNT=$(find . -name "*.log" | wc -l)
AVG_LENGTH=100 # average file name length
TOTAL_SIZE=$((FILE_COUNT * AVG_LENGTH))
if [ $TOTAL_SIZE -gt $ARG_MAX ]; then
echo "Utiliser xargs obligatoire"
find . -name "*.log" | xargs rm
else
rm *.log
fi
Rule: always xargs for bulk operations (avoids "Argument list too long").The find + xargs + grep pattern
# Basic syntax
find [path] [tests] | xargs grep [options] "pattern"
# Simple example
find . -name "*.txt" | xargs grep "ERROR"
# With line numbers
find . -name "*.log" | xargs grep -n "error"
# Case-insensitive
find . -type f | xargs grep -i "todo"
Handling spaces in names
# ❌ Without -0 (may fail)
find . -name "*.txt" | xargs grep "pattern"
# ✅ With -0 (robust)
find . -name "*.txt" -print0 | xargs -0 grep "pattern"
Useful grep options
# Files containing the pattern
find . -name "*.py" | xargs grep -l "import requests"
# Files NOT containing it
find . -name "*.js" | xargs grep -L "use strict"
# Count occurrences
find . -name "*.log" | xargs grep -c "ERROR" | grep -v :0
# Context (lines before/after)
find . -name "*.conf" | xargs grep -A 3 -B 3 "database"
DevOps cases
# Logs: search for errors
find /var/log -name "*.log" -mtime -7 -print0 | \
xargs -0 grep -h "ERROR\|FATAL" | sort | uniq -c
# Code: obsolete dependencies
find . -name "package.json" | xargs grep -l "lodash.*3\."
# Configs: hard-coded passwords (audit)
find . -name "*.yml" -o -name "*.yaml" | xargs grep -i "password.*="
# Docker: vulnerable images
find . -name "Dockerfile" | xargs grep -l "FROM.*:latest"
# Kubernetes: resources without limits
find k8s/ -name "*.yaml" | xargs grep -L "resources.limits"
Multi-patterns
# Several patterns (OR)
find . -name "*.log" | xargs grep -E "ERROR|WARN|FATAL"
# Exclusion pattern
find . -name "*.txt" | xargs grep "TODO" | grep -v "# TODO (ok)"
# Complex pattern
find . -type f | xargs grep -P '\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b' -i
# (emails)
Performance
# Parallelise the search
find . -name "*.log" -print0 | xargs -0 -n 100 -P 4 grep "ERROR"
# Limit the files processed
find . -name "*.txt" -print0 | xargs -0 -n 50 grep "pattern"
Alternatives
# 1. Recursive grep (simple)
grep -r "pattern" .
# 2. find -exec (safe)
find . -name "*.txt" -exec grep "pattern" {} +
# 3. ripgrep (very fast)
rg "pattern" --type txt
# 4. xargs (flexible)
find . -name "*.txt" | xargs grep "pattern"
Extracting results
# Files + lines
find . -name "*.py" | xargs grep -Hn "def main" > functions.txt
# File names only
find . -name "*.sh" | xargs grep -l "#!/bin/bash" > bash_scripts.txt
# Statistics
find . -name "*.log" | xargs grep "ERROR" | \
awk '{print $1}' | sort | uniq -c | sort -rn
Git integration
# Search in tracked files
git ls-files | xargs grep "FIXME"
# Modified files with a pattern
git diff --name-only | xargs grep "console.log"
# Multi-repos
find ~/projects -name .git -type d | \
xargs -I {} bash -c 'cd {}/../ && git grep "deprecated"'
Pattern: find | xargs grep = selective, fast bulk search.Common errors
| Error | Problem | Fix |
|---|---|---|
| Spaces in names | find | xargs breaks files |
-print0 | xargs -0 |
| Destructive commands | xargs rm without a test |
-p or -t or echo test |
| Empty input | Error if no file | xargs -r |
| Quotes/specials | Characters interpreted | -0 + proper quoting |
| Race conditions | Files changed during the run | Snapshots or locks |
| Resources | Too many -P processes |
Limit according to CPU |
Safety checklist
# ✅ 1. Always test with echo
find . -name "*.tmp" | xargs echo rm
# Check the output before running
# ✅ 2. Use -0 with find
find . -name "*.log" -print0 | xargs -0 command
# ✅ 3. Confirmation (-p) for destructive actions
find . -name "*.bak" | xargs -p rm
# ✅ 4. Verbose (-t) for auditing
find . -type f | xargs -t chmod 644 2>> audit.log
# ✅ 5. No-run-if-empty (-r)
find /inexistant -name "*.log" | xargs -r rm
Dangerous commands
# ❌ DANGEROUS
find / | xargs rm # Can delete everything!
find . | xargs chmod 777 # Open permissions
find . -name "*.sh" | xargs rm # Can break running scripts
# ✅ SAFE
find /tmp -name "*.tmp" -mtime +7 | xargs -r -p rm
find . -type f -name "*.txt" -print0 | xargs -0 chmod 644
find . -name "*.bak" | xargs -t rm 2>&1 | tee cleanup.log
Error handling
# Go on after an error
find . -name "*.log" | xargs -r gzip || echo "Some failed"
# Capture outputs
find . -name "*.txt" | xargs grep "pattern" 2>errors.log 1>results.log
# Exit on error
set -e
find . -name "*.conf" | xargs -r validate_config
Special characters
# Files with quotes, $, etc.
touch "file with 'quotes'.txt"
touch "file_with_$VAR.txt"
# ❌ Fails
find . -name "*.txt" | xargs cat
# ✅ Works
find . -name "*.txt" -print0 | xargs -0 cat
Resource limits
# Limit memory
find . -name "*.log" | xargs -n 100 -P 2 gzip
# Max 100 files per process, 2 processes max
# Limit CPU
find . -name "*.jpg" | xargs -P 4 -I {} nice -n 10 convert {} {}.png
# Timeout per command
find . -name "*.mp4" | xargs -I {} timeout 60s ffmpeg -i {} {}.webm
DevOps best practices
# 1. Systematic dry run
DRY_RUN=echo # or empty
find . -name "*.old" | xargs $DRY_RUN rm
# 2. Logging
find . -name "*.log" | xargs -t gzip 2>&1 | tee -a compress.log
# 3. Monitoring
START=$(date +%s)
find . -name "*.tmp" | xargs -r rm
END=$(date +%s)
echo "Cleanup took $((END-START))s" >> metrics.log
# 4. Alerting
ERRORS=$(find /data -name "*.corrupted" | wc -l)
if [ $ERRORS -gt 0 ]; then
find /data -name "*.corrupted" | xargs -I {} echo "Corrupted: {}" | \
mail -s "Data Alert" admin@
fi
Code review checklist
-
-print0 | xargs -0with find -
-rif the input can be empty -
-por-tfor destructive commands - Test with
echobefore the real run -
-Plimited according to available resources - Error handling (exit codes, logs)
- No direct wildcard (
rm *) - Absolute paths when needed
echo before a destructive run.