How to use xargs to build command lines from standard input

Even the most basic installation of any Linux distribution comes with a set of really useful utilities: “xargs” is undoubtedly one of those. By using xargs we can build and execute command lines using items from standard input as arguments of a command. This is especially useful when dealing with programs which don’t read standard input directly.

In this article we learn how to install the xargs utility on the most used Linux distributions, and how to use it to build and execute command lines.

In this tutorial you will learn:

  • How to install xargs on the most used Linux distributions
  • How to use xargs to build and execute command lines
  • How to handle whitespaces in file names
  • How to make xargs ask for confirmation before executing a command
  • How to run multiple commands on each item
How to use xargs to build command lines from standard input
How to use xargs to build command lines from standard input – original image by vectorjuice on Freepik
Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Distribution agnostic
Software findutils, xargs
Other Privileged access to your Linux system as root or via the sudo command in order to perform system-wide installation of required packages
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux-commands to be executed as a regular non-privileged user

Installation

The xargs utility is usually distributed in the findutils package, which includes another very useful command line tool: “find”. The package is part of even the most minimal installation of practically every Linux distribution, therefore we shouldn’t need to install it explicitly. If for some odd reason, however, it is missing from our system, we can pull it back using our favorite distribution package manager.



On Debian and Debian-based distributions, for example, we can use apt:

$ sudo apt install findutils

On Fedora and other distributions of the Red Hat family, we can use dnf, instead:

$ sudo dnf install findutils

The package is also available in the Archilinux “Core” repository, therefore can be installed using pacman:

$ sudo pacman -S findutils

Let’s see how we can use xargs to build and execute command lines.

Using xargs to build command lines

What the xargs utility does, exactly? Xargs uses items passed on the standard input, typically by using a pipe redirection operator, as additional arguments of a command we can specify together with some optional initial arguments. xargs syntax is pretty easy to understand:

xargs [options] [command [initial-arguments]]

Let’s see some real-world examples of xargs usage.

Example 1: Finding files with a specific extension containing a string

One typical xargs use case is when we have to search for a string in files with a specific extension. Recently, for example, I wanted to modify a WordPress theme, so I had to find in what PHP template file, the code snippet I wanted to modify was located. Here is how I used xargs to accomplish the task:

$ find theme -type f -name "*.php" | xargs grep -i "text-to-search"



Let’s see what this command does. First we used find to search recursively inside the theme directory and we instructed the utility to look for for regular files (-type f) whose name ends in “.php” (-name "*.php"), then, we piped the standard output of the command to xargs, so that the files which matched the above creteria were passed as arguments to the grep command.

At this point you may think: “the find utility has itself the -exec option, which executes a given command using the files which matches the search as arguments, why should I use xargs?”. The answer is: “because xargs is faster”. Let’s take a look at this example. Suppose we want to find all “.txt” files in the ~/.config directory containing the string “wizard”. We use the time to get time statistic on the command execution. First we use -exec:

$ time find ~/.config -type f -name "*.txt" -exec grep -i "wizard" {} \;

Here are the results:

real    0m0.031s 
user    0m0.010s 
sys     0m0.020s

Now, let’s try with xargs:

$ time find ~/.config -type f -name "*.txt" | xargs grep -i "wizard"

This time we obtain the following statistics:

real    0m0.015s 
user    0m0.006s 
sys     0m0.010s

Although time cannot be considered a proper benchmark tool, especially for short running commands, it gives us a general idea of what command is faster. The gap between the two commands would grow even bigger when searching in a larger set of files.

Example 2: creating a tarball archive containing potentially non-existing files

Here is another case in which xargs could be useful. Suppose we want to create a tarball containing certain files, which, however, may not exist. If we try to include a non existing file in a tarball, tar exits with a failure status:

$ tar -cpf archive.tar ~/nonexistingfile ~/.bashrc ~/.bash_profile
tar: Removing leading `/' from member names
tar: /home/doc/nonexistingfile: Cannot stat: No such file or directory
tar: Removing leading `/' from hard link targets
tar: Exiting with failure status due to previous errors

A possible solution is to tell tar to ignore read errors, using the --ignore-failed-read option; ignoring errors, however is seldom a good idea. A better solutions is to use “find” to look for the files we want to put in the archive:

$ find ~ -maxdepth 1 -type f \( -name .bashrc -o -name .bash_profile -o -name nonexistingfile \) | xargs tar -cpf archive.tar



In the example above I assumed the files to be all directly inside the home directory, therefore I used the -maxdepth 1 option, which actually disables recursive search. I also used the -type f filter, then, I included multiple -name filters (one for each file), separated by the -o operator, which works as a logical OR.

The “find” utility puts and implicit logical AND operator (-a) between given tests, which has an higher precedence than OR. That’s why we grouped the “name” tests inside parenthesis (escaped to avoid the shell interpreting them as special characters). If we hadn’t, the expression would have been equivalent to the following:

$ find ~ -maxdepth 1 -type f -a -name .bashrc -o -name .bash_profile -o -name nonexistingfile

In plain English this would have meant: “find a regular file named “.bashrc”, or any type of file named “.bash_profile”, or any type of file named “nonexistingfile”, in the home directory, without descending into subdirectories.

Using the strategy, if a file doesn’t exist it is not included in the list of files to be archived.

Handling whitespaces in file names

There is one thing we didn’t consider in previous examples: files whose name contains white spaces. xargs, by default, uses a whitespace character as items separator, therefore it will get confused if a file contains a whitespace as part of its name. Here is an example. Suppose there is a directory containing three files, one of those including a whitespace as part of its name:

test
├── file1.txt
├── file2.txt
└── file 3.txt

Now, suppose we want to search for a string in one of those files, like we did in the previous examples:

$ find test -type f | xargs grep -i "content"

As expected, the command will fail:

grep: test/file: No such file or directory
grep: 3.txt: No such file or directory  

See what happened? Since “file 3.txt” has a whitespace in its name, and xargs uses a whitespace as items delimiter, it considered “file” and “3.txt” as two separate items! We can solve this problem by changing the character xargs uses as delimiter.

When we combine find and xargs, we can simple use the former -print0 option: this makes so that each found item is followed by a null character (default is a newline). On the xargs side, instead, we can pass the --null (or -0) option, which instructs the utility to use a null character as items separator, and to consider quotes, backslashes and end of file strings, literally:

$ find test -type f -print0 | xargs --null grep -i "content"

As an alternative, we can use the xargs -d option, which let us specify which character we want to use as a delimiter.

Making xargs ask confirmation before executing a command

In certain cases we may want to double check the command which would be executed by xargs. In those cases we can use the -p or --interactive option. Let’s apply it to the previous example:

$ find test -type f -print0 | xargs --interactive --null grep -i "content"

Here is the result:

grep -i content test/file1.txt test/file2.txt 'test/file 3.txt'?...



The command will be executed only after an affirmative answer (by using y or Y). If we only want the command to be printed on the standard output before it is actually executed, instead, we can use the -t or --verbose option:

$ find test -type f -print0 | xargs --verbose --null grep -i "content"  
grep -i content test/file1.txt test/file2.txt 'test/file 3.txt'
test/file 3.txt:content

Running multiple commands on each item

One interesting option we can use with xargs is -I, which let us create a sort of “template”. It takes a “placeholder” as argument: each occurrence of the placeholder is replaced at runtime by items read from the standard input. As a trivial example, suppose we want to notify the user we are searching for a string in a file, before actually using the grep command. We would run:

$ find test -type f | xargs -I % /bin/sh -c 'echo "searching in %"; grep -i "content" "%"'

When the -I option is used, the “newline” character (\n) is used as separator, therefore, as you can see, we don’t need to use --null, and we can simply quote the % placeholder.

Conclusions

In this tutorial we learned how to use the xargs utility to build and execute command lines from standard input. The xargs utility ships in the “findutils” package, together with another very useful program: “find”, and is often used in conjunction with it, as we saw in the examples.