xargs is a versatile tool that you should keep handy in your Unix toolbox.
It lets you use the output of one command as the input for another command.
the following command finds all files in the current directory and its
subdirectories that have the extension
and adds them to the current git repository.
$ find . -name '*.md' | xargs git add
This is equivalent to running the command,
$ git add ./foo.md ./bar/baz.md ./qux.md ./quux/foo.md # ...and so on
A couple flags that you’ll need to know about are:
Specifies the number of arguments per invocation of the command.
Without this flag,
xargswill try to pass as many arguments as the system allows to the target command. With it,
xargswill pass at most
Narguments to the target command at a time. Try it out:
$ find . -name '*.md' | xargs echo (1) ./qux.md ./foo.md ./quux/foo.md ./bar/baz.md $ find . -name '*.md' | xargs -n 2 echo (2) ./qux.md ./foo.md ./quux/foo.md ./bar/baz.md
-nflag, all files are listed on a single line because echo received them in the same argument group.
-n 2, pairs of files are listed together because echo receives them two at a time.
PLACEHOLDERin the specified command should be replaced with the argument for that invocation.
Try it out:
$ find . -name '*.md' | xargs -I% echo "<%>" <./qux.md> <./foo.md> <./quux/foo.md> <./bar/baz.md>
Use this to move or rename files in bulk. For example, the following searches for Markdown files tagged with
#Recipes, and moves them to the recipes folder.
$ grep --include '*.md' -l '#Recipes' -r . | > xargs -I% mv % recipes/%
man xargs for a more
xargs splits file names on blanks — newlines, tabs, and spaces.
This can cause unexpected behavior if you’re operating on files
and the file names have spaces in them.
For example, the following command searches for Markdown files with the string
#backup in them and creates a
.tar file out of them.
$ grep --include '*.md' -l '#backup' -r . | > xargs tar -cvf backup.tar ./Homework.md ./TODO.md
This appears to work, but what happens if there’s a file with a space in its name?
$ grep --include '*.md' -l '#backup' -r . ./How to use xargs.md ./Homework.md ./TODO.md $ grep --include '*.md' -l '#backup' -r . | > xargs tar -cvf backup.tar tar: ./How: Cannot stat: No such file or directory tar: to: Cannot stat: No such file or directory tar: use: Cannot stat: No such file or directory tar: xargs.md: Cannot stat: No such file or directory ./Homework.md ./TODO.md tar: Exiting with failure status due to previous errors
The name "How to use xargs.md" got split into four different arguments, and that broke the tar command.
xargs supports quoting input strings to handle spaces,
but it’s rare for commands generating the output (e.g.,
to quote their output in a compatible manner.
Some versions of
xargs also support a flag to change the delimiter,
but the key phrase there is "some versions."
To solve this reliably in a manner that works across all versions of
you can use the
Specifies that the input delimits entries with null characters (
\0) instead of blanks.
xargsperforms no other quoting or splitting if this flag is set.
Let’s play with it.
First, switch the command above from grep to echo so that we can experiment with it. This is a good practice if you’re planning on performing a destructive operation like moving or deleting files.
$ grep --include '*.md' -l '#backup' -r . | > xargs -n1 echo ./How to use xargs.md ./Homework.md ./TODO.md
This splits the file names on spaces like before so we can verify our fix against it.
we find this entry:
Output a zero byte (the ASCII NUL character) instead of the character that normally follows a file name.
This looks like the puzzle piece that fits into
xargs -0's input slot.
Let’s try it out.
$ grep --include '*.md' -l '#backup' -r . -Z | > xargs -0 -n1 echo ./How to use xargs.md ./Homework.md ./TODO.md
That’s better! This works for our original backup command too.
$ grep --include '*.md' -l '#backup' -r . -Z | > xargs -0 tar -cvf backup.tar ./How to use xargs.md ./Homework.md ./TODO.md
What if you’re running something besides
Several Unix commands support equivalent flags.
Here are some,
For example, the following command backs up all markdown files in-order.
$ find . -name '*.md' -print0 | > sort -z | (1) > xargs -0 tar -cvf backup.tar ./Homework.md ./How to use xargs.md ./README.md ./TODO.md
sortis unnecessary because we’re feeding the output into
tar, but you get the point.
If you’re using
The following replaces our use of
$ rg -0 --files -g '*.md' | > sort -z | > xargs -0 tar -cvf backup.tar Homework.md How to use xargs.md README.md TODO.md
If the command you’re running doesn’t support a flag equivalent to
or you don’t remember the flag,
you can use the
tr command to turn newlines into nulls.
$ grep --include '*.md' -l '#backup' -r . | > tr '\n' '\0' | (1) > xargs -0 tar -cvf backup.tar ./How to use xargs.md ./Homework.md ./TODO.md
Turn all newlines into null characters.
Personally, I tend to use
tr even with commands that support
because I don’t always remember whether it was